From eabfcf27a24d4ee5d6baad86b1fbc30bea83af09 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Wed, 20 May 2026 10:04:58 +0100 Subject: [PATCH 01/22] Housekeeping: prod config tweaks --- configuration/config.prod.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configuration/config.prod.json b/configuration/config.prod.json index a6071362e..c8b5991e2 100644 --- a/configuration/config.prod.json +++ b/configuration/config.prod.json @@ -21,6 +21,7 @@ "SHOW_MENU": true, "SHOW_RECENT_CASES": true, "SHOW_MONITORING_CODES": false, + "SHOW_NOTIFICATIONS": true, "OS_HANDOVER_URL": "https://cps.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https://polaris.cps.gov.uk/global-components/prod/auth-handover.js", "RECENT_CASES_NAVIGATE_URL": "https://cps.outsystemsenterprise.com/WorkManagementApp/CaseOverview?CaseId={caseId}", "RECENT_CASES_LIST_LENGTH": 5, @@ -34,7 +35,7 @@ "BEACON_AD_REDIRECT_SUCCESSES_ENABLED": true, "BEACON_AD_REDIRECT_FAILURES_ENABLED": true, "SSO_SILENT_DELAY_MS": 2000, - "USER_DATA_REFRESH_PERIOD_MINS": 15, + "USER_DATA_REFRESH_PERIOD_MINS": 120, "PROBE_IFRAME_REFRESH_PERIOD_MINS": 0, "PROBE_NAVIGATOR_PERMISSIONS_REFRESH_PERIOD_MINS": 120, "CONTEXTS": [ From 1daf6cc0adcaf87093261def89fa5bf10bb449ea Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Wed, 20 May 2026 12:50:13 +0100 Subject: [PATCH 02/22] Update purge script and add ad-hoc rollback script --- scripts/purge-blob-cruft.sh | 6 +- scripts/restore-prod-from-safe.sh | 140 ++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 4 deletions(-) create mode 100755 scripts/restore-prod-from-safe.sh diff --git a/scripts/purge-blob-cruft.sh b/scripts/purge-blob-cruft.sh index 09418aca6..fdf454a07 100755 --- a/scripts/purge-blob-cruft.sh +++ b/scripts/purge-blob-cruft.sh @@ -37,9 +37,8 @@ fi # Root-level files (exact match): # global-components.js, global-components.js.map # cps-global-components.js (redirect stub) +# auth-handover.html (handover dispatcher entry HTML) # auth-handover.js, auth-handover.js.map -# global-components-msal-redirect.html (silent-MSAL same-origin termination page) -# msal-redirect.js, msal-redirect.js.map (IIFE bundle loaded by the termination page) # probe-iframe-load.html (LNA diagnostic probe page) # config.json (required) # notification.json (optional; kept if present) @@ -51,9 +50,8 @@ is_kept() { case "$1" in global-components.js|global-components.js.map) return 0 ;; cps-global-components.js) return 0 ;; + auth-handover.html) return 0 ;; auth-handover.js|auth-handover.js.map) return 0 ;; - global-components-msal-redirect.html) return 0 ;; - msal-redirect.js|msal-redirect.js.map) return 0 ;; probe-iframe-load.html) return 0 ;; config.json|notification.json) return 0 ;; preview/index.html|accessibility/index.html) return 0 ;; diff --git a/scripts/restore-prod-from-safe.sh b/scripts/restore-prod-from-safe.sh new file mode 100755 index 000000000..39e5d4668 --- /dev/null +++ b/scripts/restore-prod-from-safe.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +# +# Disaster-recovery script. Restores the `prod` blob container to the state of +# `prod-safe` (the pre-deploy backup created on 2026-05-20). +# +# Behaviour: +# 1. Copies every blob from prod-safe → prod (overwriting whatever's there). +# 2. Deletes any *canonical-named* blob in prod that isn't in prod-safe — i.e. +# files added by the bad deploy (e.g. a new auth-handover.html that the +# pre-deploy config doesn't reference). Non-canonical cruft (.git/, packages/, +# etc.) is left alone — it's invisible to nginx and not part of our concern. +# +# Operates via the same SSH-to-AWS-box route used by the analytics scripts, so +# byte movement is server-side and you don't need a local connection string. +# +# Usage: +# ./scripts/restore-prod-from-safe.sh # dry-run: shows +# # what would happen +# CONFIRM=yes ./scripts/restore-prod-from-safe.sh # actually run +# +# Requires: +# - AWS_REMOTE in infra/analytics/scripts/.env (the SSH endpoint) +# - that SSH endpoint has az CLI installed and an identity with +# Storage Blob Data Contributor on the sacpsglobalcomponents account. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +if [[ -f "$ROOT_DIR/infra/analytics/scripts/.env" ]]; then + # shellcheck disable=SC1091 + set -a; source "$ROOT_DIR/infra/analytics/scripts/.env"; set +a +fi + +if [[ -z "${AWS_REMOTE:-}" ]]; then + echo "AWS_REMOTE must be set (expected in infra/analytics/scripts/.env)" >&2 + exit 2 +fi + +ACCOUNT="${ACCOUNT:-sacpsglobalcomponents}" +CONFIRM_VAL="${CONFIRM:-no}" + +if [[ "$CONFIRM_VAL" != "yes" ]]; then + echo "DRY RUN — pass CONFIRM=yes to actually run." + echo "" +fi + +# Heredoc is single-quoted so the local shell doesn't interpolate. Local values +# are passed in as positional parameters via `bash -s …`. +ssh "$AWS_REMOTE" bash -s "$ACCOUNT" "$CONFIRM_VAL" << 'REMOTE_EOF' +set -e + +ACCOUNT="$1" +CONFIRM="$2" +SOURCE_CONTAINER="prod-safe" +DEST_CONTAINER="prod" + +# Canonical-named blob pattern. Mirrors scripts/purge-blob-cruft.sh keep-list +# plus the three msal-redirect transition files. Files in prod that match this +# pattern but aren't in prod-safe are deletion candidates (the bad deploy +# added them); files that don't match this pattern are left alone. +CANONICAL_PATTERN='^(global-components\.js(\.map)?|cps-global-components\.js|auth-handover\.(js|js\.map|html)|global-components-msal-redirect\.html|msal-redirect\.(js|js\.map)|probe-iframe-load\.html|config\.json|notification\.json|(preview|accessibility)/(index\.html|assets/.+))$' + +echo "=== prod-safe contents ===" +SAFE_LIST=$(az storage blob list \ + --account-name "$ACCOUNT" \ + --container-name "$SOURCE_CONTAINER" \ + --auth-mode login \ + --query "[].name" -o tsv) +echo "$SAFE_LIST" | sed 's/^/ /' +SAFE_COUNT=$(echo "$SAFE_LIST" | grep -c . || true) +echo "($SAFE_COUNT file(s))" +echo "" + +if [ "$SAFE_COUNT" -eq 0 ]; then + echo "prod-safe is empty — nothing to restore. Aborting." >&2 + exit 3 +fi + +echo "=== Orphans (canonical-named files in prod, not in prod-safe) ===" +echo " these would be deleted to match prod-safe state" +PROD_CANONICAL=$(az storage blob list \ + --account-name "$ACCOUNT" \ + --container-name "$DEST_CONTAINER" \ + --auth-mode login \ + --query "[].name" -o tsv \ + | grep -E "$CANONICAL_PATTERN" || true) +ORPHANS=$(comm -23 <(echo "$PROD_CANONICAL" | sort -u) <(echo "$SAFE_LIST" | sort -u) || true) +if [ -z "$ORPHANS" ]; then + echo " (none)" +else + echo "$ORPHANS" | sed 's/^/ /' +fi +echo "" + +if [ "$CONFIRM" != "yes" ]; then + echo "DRY RUN. Pass CONFIRM=yes to actually restore." + exit 0 +fi + +echo "=== Copying prod-safe -> prod ===" +echo "$SAFE_LIST" | while IFS= read -r blob; do + [ -z "$blob" ] && continue + az storage blob copy start \ + --account-name "$ACCOUNT" \ + --destination-container "$DEST_CONTAINER" \ + --destination-blob "$blob" \ + --source-container "$SOURCE_CONTAINER" \ + --source-blob "$blob" \ + --auth-mode login \ + --requires-sync true \ + --output none + echo " copied: $blob" +done +echo "" + +if [ -n "$ORPHANS" ]; then + echo "=== Deleting orphans ===" + echo "$ORPHANS" | while IFS= read -r blob; do + [ -z "$blob" ] && continue + az storage blob delete \ + --account-name "$ACCOUNT" \ + --container-name "$DEST_CONTAINER" \ + --name "$blob" \ + --auth-mode login \ + --output none + echo " deleted: $blob" + done + echo "" +fi + +echo "=== Done ===" +PROD_COUNT=$(az storage blob list \ + --account-name "$ACCOUNT" \ + --container-name "$DEST_CONTAINER" \ + --auth-mode login \ + --query "length(@)" -o tsv) +echo "prod now contains $PROD_COUNT blob(s) total (canonical + pre-existing cruft)." +REMOTE_EOF From db8aa5e63e8bd03c25714b3d819f21f007ad1943 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 11:44:15 +0100 Subject: [PATCH 03/22] Resolve pnpm install cache problem in CI-CD --- .github/workflows/sub-workflow-build-and-test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/sub-workflow-build-and-test.yml b/.github/workflows/sub-workflow-build-and-test.yml index 2f6b52df8..1c768b13f 100644 --- a/.github/workflows/sub-workflow-build-and-test.yml +++ b/.github/workflows/sub-workflow-build-and-test.yml @@ -53,6 +53,14 @@ jobs: node-version-file: ".nvmrc" cache: "pnpm" + # The hosted runner image can ship a partial Puppeteer cache (a Chrome + # version folder with no executable inside). puppeteer's install.mjs sees + # the folder and skips the download, then e2e's `puppeteer browsers install` + # postinstall rejects the missing binary and fails the whole install. Wipe + # the cache so Chrome is always downloaded clean. No-op when it's empty. + - name: Reset Puppeteer browser cache + run: rm -rf ~/.cache/puppeteer + - run: pnpm install env: CI: true From 08e12e71deb8028eba4e7df386e48ff002d851ff Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 12:02:17 +0100 Subject: [PATCH 04/22] Resolve pnpm install cache problem in CI-CD --- .../workflows/sub-workflow-build-and-test.yml | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/sub-workflow-build-and-test.yml b/.github/workflows/sub-workflow-build-and-test.yml index 1c768b13f..aa1cb0216 100644 --- a/.github/workflows/sub-workflow-build-and-test.yml +++ b/.github/workflows/sub-workflow-build-and-test.yml @@ -53,17 +53,23 @@ jobs: node-version-file: ".nvmrc" cache: "pnpm" - # The hosted runner image can ship a partial Puppeteer cache (a Chrome - # version folder with no executable inside). puppeteer's install.mjs sees - # the folder and skips the download, then e2e's `puppeteer browsers install` - # postinstall rejects the missing binary and fails the whole install. Wipe - # the cache so Chrome is always downloaded clean. No-op when it's empty. - - name: Reset Puppeteer browser cache - run: rm -rf ~/.cache/puppeteer - + # Two puppeteer@24.40.0 instances exist (resolved against typescript 5.9 + # and 6.0). Each runs a postinstall that downloads the SAME Chrome into the + # shared ~/.cache/puppeteer concurrently — the downloads race and leave a + # corrupt browser dir (folder present, executable missing), which fails the + # whole install. Skip the auto-download here; we install the browser once, + # serially, in the next step. - run: pnpm install env: CI: true + PUPPETEER_SKIP_DOWNLOAD: "true" + + # Single, serial browser install — no concurrent writers, so no race. The + # rm -rf guarantees a clean target even if a partial dir was left behind. + - name: Install Puppeteer browser + run: | + rm -rf ~/.cache/puppeteer + pnpm --filter e2e exec puppeteer browsers install - run: pnpm -w build From 49ed6eec7819a1a150b3f87b8a6d8ec17365539c Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 12:21:26 +0100 Subject: [PATCH 05/22] Resolve pnpm install cache problem in CI-CD --- .../workflows/sub-workflow-build-and-test.yml | 14 ----------- .puppeteerrc.cjs | 24 +++++++++++++++++++ e2e/package.json | 2 +- 3 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 .puppeteerrc.cjs diff --git a/.github/workflows/sub-workflow-build-and-test.yml b/.github/workflows/sub-workflow-build-and-test.yml index aa1cb0216..2f6b52df8 100644 --- a/.github/workflows/sub-workflow-build-and-test.yml +++ b/.github/workflows/sub-workflow-build-and-test.yml @@ -53,23 +53,9 @@ jobs: node-version-file: ".nvmrc" cache: "pnpm" - # Two puppeteer@24.40.0 instances exist (resolved against typescript 5.9 - # and 6.0). Each runs a postinstall that downloads the SAME Chrome into the - # shared ~/.cache/puppeteer concurrently — the downloads race and leave a - # corrupt browser dir (folder present, executable missing), which fails the - # whole install. Skip the auto-download here; we install the browser once, - # serially, in the next step. - run: pnpm install env: CI: true - PUPPETEER_SKIP_DOWNLOAD: "true" - - # Single, serial browser install — no concurrent writers, so no race. The - # rm -rf guarantees a clean target even if a partial dir was left behind. - - name: Install Puppeteer browser - run: | - rm -rf ~/.cache/puppeteer - pnpm --filter e2e exec puppeteer browsers install - run: pnpm -w build diff --git a/.puppeteerrc.cjs b/.puppeteerrc.cjs new file mode 100644 index 000000000..ab432ffdf --- /dev/null +++ b/.puppeteerrc.cjs @@ -0,0 +1,24 @@ +/** + * Puppeteer workspace configuration. + * + * `skipDownload` disables Puppeteer's *automatic* browser download that would + * otherwise run during every `pnpm install`. We have two `puppeteer` instances + * in the workspace (it's peer-resolved against both TypeScript 5.9 and 6.0), so + * their postinstall `install.mjs` scripts run concurrently and download the same + * Chrome build into the same shared `~/.cache/puppeteer` directory at the same + * time — the parallel writes corrupt it (browser folder present, executable + * missing) and fail `pnpm install` in every job that runs it (build-and-test, + * repo-security-scan, proxy-tests, …). + * + * cosmiconfig resolves this file globally (upward from the cwd), so it applies + * to every Puppeteer instance in the workspace. + * + * The browser is still installed — the `e2e` package's `postinstall` runs an + * *explicit* `puppeteer browsers install chrome`. An explicit browser argument + * bypasses `skipDownload`, so it's the single, serial downloader: one writer, + * no race. e2e launches headless full Chrome (Puppeteer's default), so Chrome is + * the only browser we need. + */ +module.exports = { + skipDownload: true, +}; diff --git a/e2e/package.json b/e2e/package.json index cd383d174..58db73a84 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -7,7 +7,7 @@ "test": "jest", "test:log": "LOG=true jest --runInBand", "test:watch": "jest --watch", - "postinstall": "npx puppeteer browsers install" + "postinstall": "npx puppeteer browsers install chrome" }, "devDependencies": { "@types/jest": "^29.5.12", From 0f776762bcf7c93f7e462b146af53b19e7d56bfe Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 13:15:53 +0100 Subject: [PATCH 06/22] Resolve pnpm install cache problem in CI-CD --- .puppeteerrc.cjs | 12 +++++++----- e2e/package.json | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.puppeteerrc.cjs b/.puppeteerrc.cjs index ab432ffdf..bffc74504 100644 --- a/.puppeteerrc.cjs +++ b/.puppeteerrc.cjs @@ -13,11 +13,13 @@ * cosmiconfig resolves this file globally (upward from the cwd), so it applies * to every Puppeteer instance in the workspace. * - * The browser is still installed — the `e2e` package's `postinstall` runs an - * *explicit* `puppeteer browsers install chrome`. An explicit browser argument - * bypasses `skipDownload`, so it's the single, serial downloader: one writer, - * no race. e2e launches headless full Chrome (Puppeteer's default), so Chrome is - * the only browser we need. + * The browser is still installed — the `e2e` package's `postinstall` runs + * `PUPPETEER_SKIP_DOWNLOAD=false npx puppeteer browsers install chrome`. That env + * var, set only for that one process, overrides the `skipDownload` here so the + * install actually downloads; the two package postinstalls (separate processes + * without the env) still read this file and skip, so there is only ever one + * writer — no race. e2e launches headless full Chrome (Puppeteer's default), so + * Chrome is the only browser we need. */ module.exports = { skipDownload: true, diff --git a/e2e/package.json b/e2e/package.json index 58db73a84..0b28198dc 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -7,7 +7,7 @@ "test": "jest", "test:log": "LOG=true jest --runInBand", "test:watch": "jest --watch", - "postinstall": "npx puppeteer browsers install chrome" + "postinstall": "PUPPETEER_SKIP_DOWNLOAD=false npx puppeteer browsers install chrome" }, "devDependencies": { "@types/jest": "^29.5.12", From 0e3fe5b2731c8fa18aaf188e81531c2c51675c84 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 13:28:44 +0100 Subject: [PATCH 07/22] Resolve pnpm install cache problem in CI-CD --- .../workflows/sub-workflow-build-and-test.yml | 14 ++++++++++++- .puppeteerrc.cjs | 20 ++++++++++++------- e2e/package.json | 2 +- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/.github/workflows/sub-workflow-build-and-test.yml b/.github/workflows/sub-workflow-build-and-test.yml index 2f6b52df8..d9ef9279a 100644 --- a/.github/workflows/sub-workflow-build-and-test.yml +++ b/.github/workflows/sub-workflow-build-and-test.yml @@ -78,9 +78,21 @@ jobs: BUILD_INFO="${BUILD_INFO}};" pnpm --filter cps-global-components run rollup --intro "$BUILD_INFO" - - run: | + # Browser auto-download is disabled workspace-wide via .puppeteerrc.cjs + # (skipDownload) to stop the two concurrent puppeteer postinstalls racing on + # the shared cache. e2e needs a real Chrome, so install it here as one + # serial step. PUPPETEER_SKIP_DOWNLOAD=false (a step-level env, which is + # reliably exported to the shell — unlike an inline prefix in a pnpm script) + # overrides skipDownload so the install actually downloads. Verify before + # running the tests so any failure is unambiguous. + - name: Install Chrome and run e2e tests + run: | cp -r ./packages/cps-global-components/dist/global-components.js ./e2e/harness + pnpm --filter e2e exec puppeteer browsers install chrome + ls -d ~/.cache/puppeteer/chrome/* || { echo "Chrome was not installed into ~/.cache/puppeteer"; exit 1; } pnpm --filter e2e test + env: + PUPPETEER_SKIP_DOWNLOAD: "false" - run: | rm -rf ./to-deploy diff --git a/.puppeteerrc.cjs b/.puppeteerrc.cjs index bffc74504..c5442d8db 100644 --- a/.puppeteerrc.cjs +++ b/.puppeteerrc.cjs @@ -13,13 +13,19 @@ * cosmiconfig resolves this file globally (upward from the cwd), so it applies * to every Puppeteer instance in the workspace. * - * The browser is still installed — the `e2e` package's `postinstall` runs - * `PUPPETEER_SKIP_DOWNLOAD=false npx puppeteer browsers install chrome`. That env - * var, set only for that one process, overrides the `skipDownload` here so the - * install actually downloads; the two package postinstalls (separate processes - * without the env) still read this file and skip, so there is only ever one - * writer — no race. e2e launches headless full Chrome (Puppeteer's default), so - * Chrome is the only browser we need. + * The browser is still installed where it's needed: + * - CI (build-and-test): a dedicated workflow step runs + * `puppeteer browsers install chrome` with a step-level + * `PUPPETEER_SKIP_DOWNLOAD=false`, which overrides the skip here and downloads + * Chrome as a single serial step (no race). A GitHub Actions step env is + * reliably exported to the shell — an inline `VAR=val` prefix in a pnpm + * postinstall script is NOT, which is why the install lives in the workflow. + * - Local dev: the `e2e` package's `postinstall` runs an explicit + * `puppeteer browsers install chrome`; an explicit browser arg installs even + * with `skipDownload` set, so developers still get Chrome on install. + * + * e2e launches headless full Chrome (Puppeteer's default), so Chrome is the only + * browser we need. */ module.exports = { skipDownload: true, diff --git a/e2e/package.json b/e2e/package.json index 0b28198dc..58db73a84 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -7,7 +7,7 @@ "test": "jest", "test:log": "LOG=true jest --runInBand", "test:watch": "jest --watch", - "postinstall": "PUPPETEER_SKIP_DOWNLOAD=false npx puppeteer browsers install chrome" + "postinstall": "npx puppeteer browsers install chrome" }, "devDependencies": { "@types/jest": "^29.5.12", From ce14775190edaa80e987993f345a96b577d0103b Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 13:57:03 +0100 Subject: [PATCH 08/22] #FCT2-17451 - refactor global-script for style --- .github/workflows/sub-workflow-build-and-test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/sub-workflow-build-and-test.yml b/.github/workflows/sub-workflow-build-and-test.yml index d9ef9279a..edfd1a633 100644 --- a/.github/workflows/sub-workflow-build-and-test.yml +++ b/.github/workflows/sub-workflow-build-and-test.yml @@ -88,6 +88,12 @@ jobs: - name: Install Chrome and run e2e tests run: | cp -r ./packages/cps-global-components/dist/global-components.js ./e2e/harness + # The setup-node pnpm cache is restored with `tar -P` (absolute paths), + # which recreates a PARTIAL ~/.cache/puppeteer/chrome/ folder (the + # dir without the executable). `puppeteer browsers install` refuses to + # repair a partial folder — it errors "folder exists but executable is + # missing" instead of re-downloading. Wipe it so the install starts clean. + rm -rf ~/.cache/puppeteer pnpm --filter e2e exec puppeteer browsers install chrome ls -d ~/.cache/puppeteer/chrome/* || { echo "Chrome was not installed into ~/.cache/puppeteer"; exit 1; } pnpm --filter e2e test From d6d1741c185a5bbc02a3fa331c13cac3af75836f Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 14:06:28 +0100 Subject: [PATCH 09/22] Resolve pnpm install cache problem in CI-CD --- .../workflows/sub-workflow-build-and-test.yml | 24 ++++++++++--------- e2e/jest-puppeteer.config.js | 10 +++++--- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/sub-workflow-build-and-test.yml b/.github/workflows/sub-workflow-build-and-test.yml index edfd1a633..22e4359d5 100644 --- a/.github/workflows/sub-workflow-build-and-test.yml +++ b/.github/workflows/sub-workflow-build-and-test.yml @@ -85,20 +85,22 @@ jobs: # reliably exported to the shell — unlike an inline prefix in a pnpm script) # overrides skipDownload so the install actually downloads. Verify before # running the tests so any failure is unambiguous. - - name: Install Chrome and run e2e tests + # Puppeteer's own browser downloader is unreliable on this runner: the + # setup-node pnpm cache is restored with `tar -P` (absolute paths) which + # recreates a partial ~/.cache/puppeteer, and a fresh `puppeteer browsers + # install` downloads the archive but leaves it un-extracted (folder present, + # executable missing). Rather than fight it, use the Chrome that ships on + # the GitHub ubuntu runner via PUPPETEER_EXECUTABLE_PATH — no download, no + # extract, no cache races. Setting that var also makes puppeteer skip any + # download. (.puppeteerrc skipDownload already stops the install-time race.) + - name: Run e2e tests against the runner's system Chrome run: | cp -r ./packages/cps-global-components/dist/global-components.js ./e2e/harness - # The setup-node pnpm cache is restored with `tar -P` (absolute paths), - # which recreates a PARTIAL ~/.cache/puppeteer/chrome/ folder (the - # dir without the executable). `puppeteer browsers install` refuses to - # repair a partial folder — it errors "folder exists but executable is - # missing" instead of re-downloading. Wipe it so the install starts clean. - rm -rf ~/.cache/puppeteer - pnpm --filter e2e exec puppeteer browsers install chrome - ls -d ~/.cache/puppeteer/chrome/* || { echo "Chrome was not installed into ~/.cache/puppeteer"; exit 1; } + CHROME_BIN="$(command -v google-chrome || command -v google-chrome-stable || command -v chromium-browser || command -v chromium || true)" + echo "System Chrome: ${CHROME_BIN:-}" + [ -n "$CHROME_BIN" ] || { echo "No system Chrome on the runner; cannot run e2e tests"; exit 1; } + export PUPPETEER_EXECUTABLE_PATH="$CHROME_BIN" pnpm --filter e2e test - env: - PUPPETEER_SKIP_DOWNLOAD: "false" - run: | rm -rf ./to-deploy diff --git a/e2e/jest-puppeteer.config.js b/e2e/jest-puppeteer.config.js index 84ed1e900..a26b75d80 100644 --- a/e2e/jest-puppeteer.config.js +++ b/e2e/jest-puppeteer.config.js @@ -6,9 +6,13 @@ export default { launch: { headless: true, args: ["--no-sandbox", "--disable-setuid-sandbox"], - // executablePath: - // process.env.PUPPETEER_EXEC_PATH || - // "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + // In CI we point Puppeteer at the runner's system Chrome (PUPPETEER_EXECUTABLE_PATH + // is set by the workflow) rather than relying on Puppeteer's own download, which + // is unreliable on the runner. Locally the var is unset, so Puppeteer falls back + // to its downloaded browser. + ...(process.env.PUPPETEER_EXECUTABLE_PATH + ? { executablePath: process.env.PUPPETEER_EXECUTABLE_PATH } + : {}), }, browserContext: "default", }; From ade759b413a845ee6322f389a41d7da7214f6ae1 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 14:22:21 +0100 Subject: [PATCH 10/22] Resolve pnpm install cache problem in CI-CD --- .puppeteerrc.cjs | 16 ++++++---------- e2e/jest-puppeteer.config.js | 10 +++------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/.puppeteerrc.cjs b/.puppeteerrc.cjs index c5442d8db..3638ea72e 100644 --- a/.puppeteerrc.cjs +++ b/.puppeteerrc.cjs @@ -13,19 +13,15 @@ * cosmiconfig resolves this file globally (upward from the cwd), so it applies * to every Puppeteer instance in the workspace. * - * The browser is still installed where it's needed: - * - CI (build-and-test): a dedicated workflow step runs - * `puppeteer browsers install chrome` with a step-level - * `PUPPETEER_SKIP_DOWNLOAD=false`, which overrides the skip here and downloads - * Chrome as a single serial step (no race). A GitHub Actions step env is - * reliably exported to the shell — an inline `VAR=val` prefix in a pnpm - * postinstall script is NOT, which is why the install lives in the workflow. + * A browser is still obtained where it's needed: + * - CI (build-and-test): the e2e step points Puppeteer at the runner's + * pre-installed Chrome via `PUPPETEER_EXECUTABLE_PATH`, so nothing is + * downloaded — Puppeteer's own downloader is unreliable on the runner. * - Local dev: the `e2e` package's `postinstall` runs an explicit * `puppeteer browsers install chrome`; an explicit browser arg installs even - * with `skipDownload` set, so developers still get Chrome on install. + * with `skipDownload` set here, so developers still get Chrome on install. * - * e2e launches headless full Chrome (Puppeteer's default), so Chrome is the only - * browser we need. + * e2e launches headless Chrome, so Chrome is the only browser we need. */ module.exports = { skipDownload: true, diff --git a/e2e/jest-puppeteer.config.js b/e2e/jest-puppeteer.config.js index a26b75d80..84ed1e900 100644 --- a/e2e/jest-puppeteer.config.js +++ b/e2e/jest-puppeteer.config.js @@ -6,13 +6,9 @@ export default { launch: { headless: true, args: ["--no-sandbox", "--disable-setuid-sandbox"], - // In CI we point Puppeteer at the runner's system Chrome (PUPPETEER_EXECUTABLE_PATH - // is set by the workflow) rather than relying on Puppeteer's own download, which - // is unreliable on the runner. Locally the var is unset, so Puppeteer falls back - // to its downloaded browser. - ...(process.env.PUPPETEER_EXECUTABLE_PATH - ? { executablePath: process.env.PUPPETEER_EXECUTABLE_PATH } - : {}), + // executablePath: + // process.env.PUPPETEER_EXEC_PATH || + // "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", }, browserContext: "default", }; From c040c508f0d39a24f1ec571e3dae1441c3f5ed49 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 15:55:21 +0100 Subject: [PATCH 11/22] Added service disruption notifications --- configuration/config.prod.notification.json | 21 ++++++++++++++++++++- configuration/config.test.notification.json | 21 +++++++++++++++------ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/configuration/config.prod.notification.json b/configuration/config.prod.notification.json index d3e0ee793..f879d7df9 100644 --- a/configuration/config.prod.notification.json +++ b/configuration/config.prod.notification.json @@ -1,3 +1,22 @@ { - "notifications": [] + "notifications": [ + { + "id": "network-upgrade-2026-06-01", + "heading": "Planned network upgrade", + "bodyHtml": "

A planned network upgrade will take place on Monday 1 June from 19:00 to 23:00. We are expecting a small number of brief interruptions to service during this window while the new network devices are installed and tested. If you need to work during this time, please save your work frequently.

", + "from": "2026-06-01T17:45:00Z", + "to": "2026-06-01T22:00:00Z", + "urlRegex": "^https://[^/]+\\.outsystemsenterprise\\.com/(CaseReview/LandingPage|Casework_Blocks/home)", + "dismissible": true + }, + { + "id": "network-upgrade-2026-06-04", + "heading": "Planned network upgrade", + "bodyHtml": "

A planned network upgrade will take place on Thursday 4 June from 19:00 to 23:00. We are expecting a small number of brief interruptions to service during this window while the new network devices are installed and tested. If you need to work during this time, please save your work frequently.

", + "from": "2026-06-04T17:45:00Z", + "to": "2026-06-04T22:00:00Z", + "urlRegex": "^https://[^/]+\\.outsystemsenterprise\\.com/(CaseReview/LandingPage|Casework_Blocks/home)", + "dismissible": true + } + ] } diff --git a/configuration/config.test.notification.json b/configuration/config.test.notification.json index 68c8c4b36..89e6e8d1b 100644 --- a/configuration/config.test.notification.json +++ b/configuration/config.test.notification.json @@ -1,14 +1,23 @@ { "notifications": [ { - "id": "outage-smoke-2026-05-19", - "heading": "Planned outage: Sunday 31st May", - "bodyHtml": "

The service will not be available on Sunday 31st May between 00:01 and 11:00.

", - "from": "2026-05-19T11:00:00Z", - "to": "2026-05-19T12:00:00Z", - "urlRegex": "^https://[^/]+\\.outsystemsenterprise\\.com/Casework_Blocks/home", + "id": "network-upgrade-2026-06-01", + "heading": "Planned network upgrade", + "bodyHtml": "

A planned network upgrade will take place on Monday 1 June from 19:00 to 23:00. We are expecting a small number of brief interruptions to the service during this window while the new network devices are installed and tested. If you need to work during this time, please save your work frequently.

", + "from": "2026-06-01T14:45:00Z", + "to": "2026-06-01T22:00:00Z", + "urlRegex": "(?i:^https://[^/]+\\.outsystemsenterprise\\.com/Casework_Blocks/)", "dismissible": true, "previewModeRequired": true + }, + { + "id": "network-upgrade-2026-06-04", + "heading": "Planned network upgrade", + "bodyHtml": "

A planned network upgrade will take place on Thursday 4 June from 19:00 to 23:00. We are expecting a small number of brief interruptions to the service during this window while the new network devices are installed and tested. If you need to work during this time, please save your work frequently.

", + "from": "2026-06-04T17:45:00Z", + "to": "2026-06-04T22:00:00Z", + "urlRegex": "^https://[^/]+\\.outsystemsenterprise\\.com/(CaseReview/LandingPage|Casework_Blocks/home)", + "dismissible": true } ] } From 9ddfe563f9eac73210f44022b4ff0722c3e65ad8 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 1 Jun 2026 16:11:52 +0100 Subject: [PATCH 12/22] Added service disruption notifications --- configuration/config.prod.notification.json | 4 ++-- configuration/config.test.notification.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/configuration/config.prod.notification.json b/configuration/config.prod.notification.json index f879d7df9..e4c57c7aa 100644 --- a/configuration/config.prod.notification.json +++ b/configuration/config.prod.notification.json @@ -3,7 +3,7 @@ { "id": "network-upgrade-2026-06-01", "heading": "Planned network upgrade", - "bodyHtml": "

A planned network upgrade will take place on Monday 1 June from 19:00 to 23:00. We are expecting a small number of brief interruptions to service during this window while the new network devices are installed and tested. If you need to work during this time, please save your work frequently.

", + "bodyHtml": "

There are CPS wide upgrades happening tonight (Monday the 1st of June between 1900 and 2300). During this time period we are expecting a small number of brief interruptions to service. If you are working in these new tools please be aware that CMS Classic may disconnect whilst the upgrades are taking place. If you are logged out of CMS Classic you will not be able to progress in the new tools, so please keep checking the CMS Classic tab is still logged in.

", "from": "2026-06-01T17:45:00Z", "to": "2026-06-01T22:00:00Z", "urlRegex": "^https://[^/]+\\.outsystemsenterprise\\.com/(CaseReview/LandingPage|Casework_Blocks/home)", @@ -12,7 +12,7 @@ { "id": "network-upgrade-2026-06-04", "heading": "Planned network upgrade", - "bodyHtml": "

A planned network upgrade will take place on Thursday 4 June from 19:00 to 23:00. We are expecting a small number of brief interruptions to service during this window while the new network devices are installed and tested. If you need to work during this time, please save your work frequently.

", + "bodyHtml": "

There are CPS wide upgrades happening tonight (Thursday the 4th of June between 1900 and 2300). During this time period we are expecting a small number of brief interruptions to service. If you are working in these new tools please be aware that CMS Classic may disconnect whilst the upgrades are taking place. If you are logged out of CMS Classic you will not be able to progress in the new tools, so please keep checking the CMS Classic tab is still logged in.

", "from": "2026-06-04T17:45:00Z", "to": "2026-06-04T22:00:00Z", "urlRegex": "^https://[^/]+\\.outsystemsenterprise\\.com/(CaseReview/LandingPage|Casework_Blocks/home)", diff --git a/configuration/config.test.notification.json b/configuration/config.test.notification.json index 89e6e8d1b..4e8565a33 100644 --- a/configuration/config.test.notification.json +++ b/configuration/config.test.notification.json @@ -3,7 +3,7 @@ { "id": "network-upgrade-2026-06-01", "heading": "Planned network upgrade", - "bodyHtml": "

A planned network upgrade will take place on Monday 1 June from 19:00 to 23:00. We are expecting a small number of brief interruptions to the service during this window while the new network devices are installed and tested. If you need to work during this time, please save your work frequently.

", + "bodyHtml": "

There are CPS wide upgrades happening tonight (Monday the 1st of June between 1900 and 2300). During this time period we are expecting a small number of brief interruptions to service. If you are working in these new tools please be aware that CMS Classic may disconnect whilst the upgrades are taking place. If you are logged out of CMS Classic you will not be able to progress in the new tools, so please keep checking the CMS Classic tab is still logged in.

", "from": "2026-06-01T14:45:00Z", "to": "2026-06-01T22:00:00Z", "urlRegex": "(?i:^https://[^/]+\\.outsystemsenterprise\\.com/Casework_Blocks/)", @@ -13,7 +13,7 @@ { "id": "network-upgrade-2026-06-04", "heading": "Planned network upgrade", - "bodyHtml": "

A planned network upgrade will take place on Thursday 4 June from 19:00 to 23:00. We are expecting a small number of brief interruptions to the service during this window while the new network devices are installed and tested. If you need to work during this time, please save your work frequently.

", + "bodyHtml": "

There are CPS wide upgrades happening tonight (Thursday the 4th of June between 1900 and 2300). During this time period we are expecting a small number of brief interruptions to service. If you are working in these new tools please be aware that CMS Classic may disconnect whilst the upgrades are taking place. If you are logged out of CMS Classic you will not be able to progress in the new tools, so please keep checking the CMS Classic tab is still logged in.

", "from": "2026-06-04T17:45:00Z", "to": "2026-06-04T22:00:00Z", "urlRegex": "^https://[^/]+\\.outsystemsenterprise\\.com/(CaseReview/LandingPage|Casework_Blocks/home)", From 88531b52720378ff87f00310933bd5fac95b0f20 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Tue, 9 Jun 2026 11:38:02 +0100 Subject: [PATCH 13/22] Housekeeping: update kql dependencies --- infra/analytics/kql/dependencies.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/infra/analytics/kql/dependencies.md b/infra/analytics/kql/dependencies.md index 12f4555af..eb022297b 100644 --- a/infra/analytics/kql/dependencies.md +++ b/infra/analytics/kql/dependencies.md @@ -9,12 +9,17 @@ GloCo_PageViews |---> GloCo_PageViews_CaseReview | | | |---> GloCo_CaseReview_TotalStartedSubmitted - | |---> GloCo_CaseReview_WithTriageTotalStartedSubmitted - | |---> GloCo_CaseReview_PerDay + | |---> GloCo_CaseReview_WithTriageTotalStartedSubmitted (also joins AppEvents — triage-submission) + | |---> GloCo_CaseReview_InvolvementByUser (also joins AppEvents — triage-submission) | |---> GloCo_CaseReview_AreaCounts (also joins GloCo__UserAreaMapping) - | |---> GloCo_CaseReview_Duration + | | + | '---> GloCo_CaseReview_PerCase | | - | '---> GloCo_CaseReview_Duration_Chart + | |---> GloCo_CaseReview_Duration + | | | + | | '---> GloCo_CaseReview_Duration_Chart + | |---> GloCo_CaseReview_PerDay + | '---> GloCo_CaseReview_Submissions | |---> GloCo_App_UsersPerDay | | @@ -24,7 +29,7 @@ GloCo_PageViews | | | '---> GloCo_Users_UsageDistribution_Chart | - |---> GloCo_Users_VisitsPerApp + |---> GloCo_Users_VisitsPerApp (also joins GloCo_UserAreas) | |---> GloCo_PageViews_ActiveUsers_Chart | @@ -51,7 +56,11 @@ AppEvents | |---> GloCo__IframeProbeEvents (also feeds GloCo__UserAuthStatus above) | - '---> GloCo__Users_EdgePolicyCorrupt (also joins GloCo_PageViews above) + |---> GloCo__Users_EdgePolicyCorrupt (also joins GloCo_PageViews above) + | + |---> GloCo_CaseReview_WithTriageTotalStartedSubmitted (also reads GloCo_PageViews_CaseReview above) + | + '---> GloCo_CaseReview_InvolvementByUser (also reads GloCo_PageViews_CaseReview above) ``` ``` From 039478b6e92f76db7dbeafc3223c99e0c9160b2c Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Tue, 16 Jun 2026 10:45:14 +0100 Subject: [PATCH 14/22] Header tin QA to go to CPS blue --- configuration/config.test.json | 1 + 1 file changed, 1 insertion(+) diff --git a/configuration/config.test.json b/configuration/config.test.json index 66e44a302..afc5d9d95 100644 --- a/configuration/config.test.json +++ b/configuration/config.test.json @@ -27,6 +27,7 @@ "SHOW_RECENT_CASES": true, "SHOW_MONITORING_CODES": true, "SHOW_NOTIFICATIONS": true, + "SHOW_HEADER_REBRAND": "cps", "OS_HANDOVER_URL": "https://cps-tst.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https://polaris-qa-notprod.cps.gov.uk/global-components/test/auth-handover.js", "RECENT_CASES_NAVIGATE_URL": "https://cps-tst.outsystemsenterprise.com/WorkManagementApp/CaseOverview?CaseId={caseId}", "RECENT_CASES_LIST_LENGTH": 5, From 61930b46302fb751310fd2452af452d1ebd38713 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Wed, 17 Jun 2026 12:19:24 +0100 Subject: [PATCH 15/22] Update OS skip link targets --- configuration/config.dev.json | 6 ++--- configuration/config.prod.json | 6 ++--- configuration/config.test.json | 6 ++--- configuration/config.uat.json | 6 ++--- e2e/tests/skip-links.test.ts | 47 ++++++++++++++++++++++------------ 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/configuration/config.dev.json b/configuration/config.dev.json index 6cabc4a33..aaac7760b 100644 --- a/configuration/config.dev.json +++ b/configuration/config.dev.json @@ -54,9 +54,9 @@ "box-shadow": "none" }, "skipLinks": { - "mainSelector": ".cps-skip-link__target:not(.cps-filter-layout)", - "searchSelector": ".cps-skip-link__target.cps-filter-layout.cps-search-form", - "listSelector": ".cps-skip-link__target.cps-filter-layout.cps-search-results", + "mainSelector": ".cps-skip-link__target", + "searchSelector": ".cps-skip-link__target .cps-filter-layout .cps-search-form", + "listSelector": ".cps-skip-link__target .cps-filter-layout .cps-search-results", "useScroll": true }, "msalRedirectUrl": "https://cps-dev.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https%3A%2F%2Fpolaris-qa-notprod.cps.gov.uk%2Fglobal-components%2Fdev%2Fauth-handover.js&stage=ad-redirect", diff --git a/configuration/config.prod.json b/configuration/config.prod.json index 7d595f7f1..f4da9c32a 100644 --- a/configuration/config.prod.json +++ b/configuration/config.prod.json @@ -50,9 +50,9 @@ "box-shadow": "none" }, "skipLinks": { - "mainSelector": ".cps-skip-link__target:not(.cps-filter-layout)", - "searchSelector": ".cps-skip-link__target.cps-filter-layout.cps-search-form", - "listSelector": ".cps-skip-link__target.cps-filter-layout.cps-search-results", + "mainSelector": ".cps-skip-link__target", + "searchSelector": ".cps-skip-link__target .cps-filter-layout .cps-search-form", + "listSelector": ".cps-skip-link__target .cps-filter-layout .cps-search-results", "useScroll": true }, "msalRedirectUrl": "https://cps.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https%3A%2F%2Fpolaris.cps.gov.uk%2Fglobal-components%2Fprod%2Fauth-handover.js&stage=ad-redirect", diff --git a/configuration/config.test.json b/configuration/config.test.json index 254e9d5c6..bb7fee18a 100644 --- a/configuration/config.test.json +++ b/configuration/config.test.json @@ -57,9 +57,9 @@ "box-shadow": "none" }, "skipLinks": { - "mainSelector": ".cps-skip-link__target:not(.cps-filter-layout)", - "searchSelector": ".cps-skip-link__target.cps-filter-layout.cps-search-form", - "listSelector": ".cps-skip-link__target.cps-filter-layout.cps-search-results", + "mainSelector": ".cps-skip-link__target", + "searchSelector": ".cps-skip-link__target .cps-filter-layout .cps-search-form", + "listSelector": ".cps-skip-link__target .cps-filter-layout .cps-search-results", "useScroll": true }, "msalRedirectUrl": "https://cps-tst.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https%3A%2F%2Fpolaris-qa-notprod.cps.gov.uk%2Fglobal-components%2Ftest%2Fauth-handover.js&stage=ad-redirect", diff --git a/configuration/config.uat.json b/configuration/config.uat.json index a60ddce69..c2332cf8e 100644 --- a/configuration/config.uat.json +++ b/configuration/config.uat.json @@ -57,9 +57,9 @@ "box-shadow": "none" }, "skipLinks": { - "mainSelector": ".cps-skip-link__target:not(.cps-filter-layout)", - "searchSelector": ".cps-skip-link__target.cps-filter-layout.cps-search-form", - "listSelector": ".cps-skip-link__target.cps-filter-layout.cps-search-results", + "mainSelector": ".cps-skip-link__target", + "searchSelector": ".cps-skip-link__target .cps-filter-layout .cps-search-form", + "listSelector": ".cps-skip-link__target .cps-filter-layout .cps-search-results", "useScroll": true }, "msalRedirectUrl": "https://cps-tst1.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https%3A%2F%2Fpolaris-uat-notprod.cps.gov.uk%2Fglobal-components%2Fuat%2Fauth-handover.js&stage=ad-redirect", diff --git a/e2e/tests/skip-links.test.ts b/e2e/tests/skip-links.test.ts index a39be72d0..615287157 100644 --- a/e2e/tests/skip-links.test.ts +++ b/e2e/tests/skip-links.test.ts @@ -72,6 +72,15 @@ const injectTarget = ({ className, id }: { className?: string; id?: string }) => id, ); +// Appends a raw HTML fragment to the top-level document body (for nested ancestor chains +// that descendant selectors resolve against). +const injectHtml = (html: string) => + page.evaluate(markup => { + const wrapper = document.createElement("div"); + wrapper.innerHTML = markup; + document.body.appendChild(wrapper); + }, html); + describe("Skip links", () => { it("renders only the main skip link when no targets are present", async () => { await arrange(skipLinksSettings); @@ -113,18 +122,19 @@ describe("Skip links", () => { }); }); -// Reproduces the real OutSystems config, where all three targets share `cps-skip-link__target` -// and search/list additionally share `cps-filter-layout`. The main selector disambiguates via -// :not(.cps-filter-layout); search/list pin the distinguishing class. This guards the actual -// production selectors against querySelector / arrive edge cases with :not() and compound classes. +// Reproduces the real OutSystems config: `cps-skip-link__target` is an ancestor of +// `cps-filter-layout`, which is an ancestor of the actual `cps-search-form` / `cps-search-results` +// targets. The selectors are descendant chains, so querySelector resolves each to the rightmost +// (innermost) element. Main resolves to the outer container. This guards the production selectors +// against querySelector / arrive edge cases with descendant combinators. const osStyleSettings: DeepPartial = { config: { CONTEXTS: [ { skipLinks: { - mainSelector: ".cps-skip-link__target:not(.cps-filter-layout)", - searchSelector: ".cps-skip-link__target.cps-filter-layout.cps-search-form", - listSelector: ".cps-skip-link__target.cps-filter-layout.cps-search-results", + mainSelector: ".cps-skip-link__target", + searchSelector: ".cps-skip-link__target .cps-filter-layout .cps-search-form", + listSelector: ".cps-skip-link__target .cps-filter-layout .cps-search-results", useScroll: true, }, }, @@ -132,29 +142,34 @@ const osStyleSettings: DeepPartial = { }, }; -describe("Skip links (OutSystems-style shared-class selectors)", () => { - it("shows only the main link when just the bare cps-skip-link__target element is present", async () => { +describe("Skip links (OutSystems-style nested-descendant selectors)", () => { + it("shows only the main link when the cps-skip-link__target container has no filter-layout descendants", async () => { await arrange(osStyleSettings); await act(); - // The main target carries ONLY cps-skip-link__target (no cps-filter-layout). - await injectTarget({ className: "cps-skip-link__target" }); + // The main container exists, but with no nested cps-filter-layout/search/list descendants. + await injectHtml(``); await waitForSkipLink("Skip to main content"); await new Promise(r => setTimeout(r, 300)); - // search/list selectors require their distinguishing classes, so they must not match this element. + // search/list selectors require the nested descendant chain, so they must not match. expect(await getSkipLinkTexts()).toEqual(["Skip to main content"]); // A real mainSelector is configured, so no fallback target is synthesised. expect(await fallbackTargetExists()).toBe(false); }); - it("shows all three links when the real search/list elements are present", async () => { + it("shows all three links when the nested search/list elements are present", async () => { await arrange(osStyleSettings); await act(); - await injectTarget({ className: "cps-skip-link__target" }); - await injectTarget({ className: "cps-skip-link__target cps-filter-layout cps-search-form" }); - await injectTarget({ className: "cps-skip-link__target cps-filter-layout cps-search-results" }); + // Realistic tree: one outer target, a filter-layout, with the search + list targets nested inside. + await injectHtml(` + `); await waitForSkipLink("Skip to main content"); await waitForSkipLink("Skip to case search"); From dd1a4a0f16038ac33e573740f367a67c91d1c117 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Thu, 25 Jun 2026 13:44:30 +0100 Subject: [PATCH 16/22] Housekeeping - switch off monitoring codes in all environments --- configuration/config.dev.json | 2 +- configuration/config.test.json | 2 +- configuration/config.uat.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configuration/config.dev.json b/configuration/config.dev.json index 40f320a4b..9103bae5f 100644 --- a/configuration/config.dev.json +++ b/configuration/config.dev.json @@ -31,7 +31,7 @@ "ACCESSIBILITY_STATEMENT_URL": "https://cps-dev.outsystemsenterprise.com/Casework/accessibility", "SHOW_MENU": true, "SHOW_RECENT_CASES": true, - "SHOW_MONITORING_CODES": true, + "SHOW_MONITORING_CODES": false, "SHOW_HEADER_REBRAND": "cps", "SHOW_CASE_DETAILS": "b", "OS_HANDOVER_URL": "https://cps-dev.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https://polaris-qa-notprod.cps.gov.uk/global-components/dev/auth-handover.js", diff --git a/configuration/config.test.json b/configuration/config.test.json index 8649d87d3..9a03dccac 100644 --- a/configuration/config.test.json +++ b/configuration/config.test.json @@ -26,7 +26,7 @@ "ACCESSIBILITY_STATEMENT_URL": "https://cps-tst.outsystemsenterprise.com/Casework/accessibility", "SHOW_MENU": true, "SHOW_RECENT_CASES": true, - "SHOW_MONITORING_CODES": true, + "SHOW_MONITORING_CODES": false, "SHOW_NOTIFICATIONS": true, "SHOW_HEADER_REBRAND": "cps", "SHOW_CASE_DETAILS": "b", diff --git a/configuration/config.uat.json b/configuration/config.uat.json index 3a430b91c..3be5717bb 100644 --- a/configuration/config.uat.json +++ b/configuration/config.uat.json @@ -26,7 +26,7 @@ "ACCESSIBILITY_STATEMENT_URL": "https://cps-tst1.outsystemsenterprise.com/Casework/accessibility", "SHOW_MENU": true, "SHOW_RECENT_CASES": true, - "SHOW_MONITORING_CODES": true, + "SHOW_MONITORING_CODES": false, "SHOW_NOTIFICATIONS": true, "SHOW_HEADER_REBRAND": "cps", "SHOW_CASE_DETAILS": "b", From b49293b111b67ba6b5d5da8742c25b6992b4249a Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Fri, 26 Jun 2026 09:26:04 +0100 Subject: [PATCH 17/22] Housekeeping - stop showing case details in pre-prod --- configuration/config.dev.json | 1 - configuration/config.test.json | 1 - configuration/config.uat.json | 1 - 3 files changed, 3 deletions(-) diff --git a/configuration/config.dev.json b/configuration/config.dev.json index 9103bae5f..8ed09f6e8 100644 --- a/configuration/config.dev.json +++ b/configuration/config.dev.json @@ -33,7 +33,6 @@ "SHOW_RECENT_CASES": true, "SHOW_MONITORING_CODES": false, "SHOW_HEADER_REBRAND": "cps", - "SHOW_CASE_DETAILS": "b", "OS_HANDOVER_URL": "https://cps-dev.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https://polaris-qa-notprod.cps.gov.uk/global-components/dev/auth-handover.js", "RECENT_CASES_NAVIGATE_URL": "https://cps-dev.outsystemsenterprise.com/WorkManagementApp/CaseOverview?CaseId={caseId}", "RECENT_CASES_LIST_LENGTH": 5, diff --git a/configuration/config.test.json b/configuration/config.test.json index 9a03dccac..0dcb8aecb 100644 --- a/configuration/config.test.json +++ b/configuration/config.test.json @@ -29,7 +29,6 @@ "SHOW_MONITORING_CODES": false, "SHOW_NOTIFICATIONS": true, "SHOW_HEADER_REBRAND": "cps", - "SHOW_CASE_DETAILS": "b", "OS_HANDOVER_URL": "https://cps-tst.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https://polaris-qa-notprod.cps.gov.uk/global-components/test/auth-handover.js", "RECENT_CASES_NAVIGATE_URL": "https://cps-tst.outsystemsenterprise.com/WorkManagementApp/CaseOverview?CaseId={caseId}", "RECENT_CASES_LIST_LENGTH": 5, diff --git a/configuration/config.uat.json b/configuration/config.uat.json index 3be5717bb..3bfea0825 100644 --- a/configuration/config.uat.json +++ b/configuration/config.uat.json @@ -29,7 +29,6 @@ "SHOW_MONITORING_CODES": false, "SHOW_NOTIFICATIONS": true, "SHOW_HEADER_REBRAND": "cps", - "SHOW_CASE_DETAILS": "b", "OS_HANDOVER_URL": "https://cps-tst1.outsystemsenterprise.com/Casework_Patterns/auth-handover.html?src=https://polaris-uat-notprod.cps.gov.uk/global-components/uat/auth-handover.js", "RECENT_CASES_NAVIGATE_URL": "https://cps-tst1.outsystemsenterprise.com/WorkManagementApp/CaseOverview?CaseId={caseId}", "RECENT_CASES_LIST_LENGTH": 5, From 428ee9e18068b602b95ac5646f3ad6c2a7455a6a Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 29 Jun 2026 08:58:51 +0100 Subject: [PATCH 18/22] Disable case locking poc --- configuration/config.dev.json | 7 +------ configuration/config.test.json | 2 +- configuration/config.uat.json | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/configuration/config.dev.json b/configuration/config.dev.json index 8ed09f6e8..fb28551fc 100644 --- a/configuration/config.dev.json +++ b/configuration/config.dev.json @@ -13,12 +13,7 @@ "CASE_LOCKING_POC_SCRIPT_BLOB_ADDRESS": "", "CASE_LOCKING_API_URL": "https://polaris-qa-notprod.cps.gov.uk/global-components/case-locking/api/section-view", "FEATURE_FLAG_CASE_LOCKING_USERS": { - "adHocUserObjectIds": [ - "4faa102c-47fb-4631-80e8-6038d46de0b0", - "4acc9fb2-3e32-4109-b3d1-5fcd3a253e4e", - "87a7b627-cbac-4fcc-a452-371142bd7ac3", - "d900ce0d-5dc1-4800-9a6c-72cbc01005fa" - ] + "generallyAvailable": false }, "BANNER_TITLE_HREF": "https://cps-dev.outsystemsenterprise.com/Casework/home", "AD_TENANT_AUTHORITY": "https://login.microsoftonline.com/00dd0d1d-d7e6-4338-ac51-565339c7088c", diff --git a/configuration/config.test.json b/configuration/config.test.json index 0dcb8aecb..a0c8eac7c 100644 --- a/configuration/config.test.json +++ b/configuration/config.test.json @@ -13,7 +13,7 @@ "CASE_LOCKING_POC_SCRIPT_BLOB_ADDRESS": "", "CASE_LOCKING_API_URL": "https://polaris-qa-notprod.cps.gov.uk/global-components/case-locking/api/section-view", "FEATURE_FLAG_CASE_LOCKING_USERS": { - "generallyAvailable": true + "generallyAvailable": false }, "BANNER_TITLE_HREF": "https://cps-tst.outsystemsenterprise.com/Casework/home", "AD_TENANT_AUTHORITY": "https://login.microsoftonline.com/00dd0d1d-d7e6-4338-ac51-565339c7088c", diff --git a/configuration/config.uat.json b/configuration/config.uat.json index 3bfea0825..dd7fd18db 100644 --- a/configuration/config.uat.json +++ b/configuration/config.uat.json @@ -13,7 +13,7 @@ "CASE_LOCKING_POC_SCRIPT_BLOB_ADDRESS": "", "CASE_LOCKING_API_URL": "https://polaris-uat-notprod.cps.gov.uk/global-components/case-locking/api/section-view", "FEATURE_FLAG_CASE_LOCKING_USERS": { - "generallyAvailable": true + "generallyAvailable": false }, "BANNER_TITLE_HREF": "https://cps-tst1.outsystemsenterprise.com/Casework/home", "AD_TENANT_AUTHORITY": "https://login.microsoftonline.com/00dd0d1d-d7e6-4338-ac51-565339c7088c", From 9b9009faafe09dc132fd772551ee0146c16a3e41 Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Thu, 2 Jul 2026 09:45:13 +0100 Subject: [PATCH 19/22] Update prod report an issue link --- configuration/config.prod.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/config.prod.json b/configuration/config.prod.json index db8d7d311..e25be5dbc 100644 --- a/configuration/config.prod.json +++ b/configuration/config.prod.json @@ -17,7 +17,7 @@ "GATEWAY_URL": "https://polaris.cps.gov.uk/", "APP_INSIGHTS_CONNECTION_STRING": "InstrumentationKey=e572c03c-8d38-4771-b193-962f13da1b1a;IngestionEndpoint=https://polaris.cps.gov.uk/global-components/analytics/;ApplicationId=3dafc37d-8c9c-4480-90fc-532ac2b8bba2", "SURVEY_LINK": "https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjMYFiPHggxZNtgEAXRbAx2hUNjNWUDc0UEtVTERCMFpFMkI1QTVSSUxTVyQlQCN0PWcu&rc715f3f34a484d8eae0a1809b30c6c5a=%22Feedback%22&r67e8aaf2389e4b9d8aa0c01902281211=9&rf690462c782847db972938cb1c773bc2=%22Easy%22&r781f1b5b16b14f5689b788fa6d98287b=%22Make%20a%20Charging%20Decision%22", - "REPORT_ISSUE_LINK": "", + "REPORT_ISSUE_LINK": "https://forms.office.com/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjJoWrYUO2RtKg-k_WWF_5qZUMFhXNTRVNkowWDhXSzFMOUc5SVhQQlVMSy4u", "ACCESSIBILITY_STATEMENT_URL": "https://polaris.cps.gov.uk/global-components/prod/accessibility/statement.html", "SHOW_MENU": true, "SHOW_RECENT_CASES": true, From 50e161c6f1d26c377390f4b30b027c214108be7c Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Thu, 2 Jul 2026 09:47:32 +0100 Subject: [PATCH 20/22] Update prod report an issue link --- configuration/config.dev.json | 2 +- configuration/config.test.json | 2 +- configuration/config.uat.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configuration/config.dev.json b/configuration/config.dev.json index 73019e133..9c62e3a47 100644 --- a/configuration/config.dev.json +++ b/configuration/config.dev.json @@ -22,7 +22,7 @@ "GATEWAY_URL": "https://polaris-qa-notprod.cps.gov.uk/", "APP_INSIGHTS_CONNECTION_STRING": "InstrumentationKey=e572c03c-8d38-4771-b193-962f13da1b1a;IngestionEndpoint=https://polaris-qa-notprod.cps.gov.uk/global-components/analytics/;ApplicationId=3dafc37d-8c9c-4480-90fc-532ac2b8bba2", "SURVEY_LINK": "https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjMYFiPHggxZNtgEAXRbAx2hUNjNWUDc0UEtVTERCMFpFMkI1QTVSSUxTVyQlQCN0PWcu&rc715f3f34a484d8eae0a1809b30c6c5a=%22Feedback%22&r67e8aaf2389e4b9d8aa0c01902281211=9&rf690462c782847db972938cb1c773bc2=%22Easy%22&r781f1b5b16b14f5689b788fa6d98287b=%22Make%20a%20Charging%20Decision%22", - "REPORT_ISSUE_LINK": "https://cpsprod1.service-now.com/sp?id=sc_cat_item&sys_id=716b2e011bd66410d5b3dce3b24bcbbf", + "REPORT_ISSUE_LINK": "https://forms.office.com/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjJoWrYUO2RtKg-k_WWF_5qZUMFhXNTRVNkowWDhXSzFMOUc5SVhQQlVMSy4u", "ACCESSIBILITY_STATEMENT_URL": "https://cps-dev.outsystemsenterprise.com/Casework/accessibility", "SHOW_MENU": true, "SHOW_RECENT_CASES": true, diff --git a/configuration/config.test.json b/configuration/config.test.json index 84dc9d9d1..5922cccbd 100644 --- a/configuration/config.test.json +++ b/configuration/config.test.json @@ -22,7 +22,7 @@ "GATEWAY_URL": "https://polaris-qa-notprod.cps.gov.uk/", "APP_INSIGHTS_CONNECTION_STRING": "InstrumentationKey=e572c03c-8d38-4771-b193-962f13da1b1a;IngestionEndpoint=https://polaris-qa-notprod.cps.gov.uk/global-components/analytics/;ApplicationId=3dafc37d-8c9c-4480-90fc-532ac2b8bba2", "SURVEY_LINK": "https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjMYFiPHggxZNtgEAXRbAx2hUNjNWUDc0UEtVTERCMFpFMkI1QTVSSUxTVyQlQCN0PWcu&rc715f3f34a484d8eae0a1809b30c6c5a=%22Feedback%22&r67e8aaf2389e4b9d8aa0c01902281211=9&rf690462c782847db972938cb1c773bc2=%22Easy%22&r781f1b5b16b14f5689b788fa6d98287b=%22Make%20a%20Charging%20Decision%22", - "REPORT_ISSUE_LINK": "https://cpsprod1.service-now.com/sp?id=sc_cat_item&sys_id=716b2e011bd66410d5b3dce3b24bcbbf", + "REPORT_ISSUE_LINK": "https://forms.office.com/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjJoWrYUO2RtKg-k_WWF_5qZUMFhXNTRVNkowWDhXSzFMOUc5SVhQQlVMSy4u", "ACCESSIBILITY_STATEMENT_URL": "https://cps-tst.outsystemsenterprise.com/Casework/accessibility", "SHOW_MENU": true, "SHOW_RECENT_CASES": true, diff --git a/configuration/config.uat.json b/configuration/config.uat.json index c4cf3b5ca..617df8a63 100644 --- a/configuration/config.uat.json +++ b/configuration/config.uat.json @@ -22,7 +22,7 @@ "GATEWAY_URL": "https://polaris-uat-notprod.cps.gov.uk/", "APP_INSIGHTS_CONNECTION_STRING": "InstrumentationKey=e572c03c-8d38-4771-b193-962f13da1b1a;IngestionEndpoint=https://polaris-uat-notprod.cps.gov.uk/global-components/analytics/;ApplicationId=3dafc37d-8c9c-4480-90fc-532ac2b8bba2", "SURVEY_LINK": "https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjMYFiPHggxZNtgEAXRbAx2hUNjNWUDc0UEtVTERCMFpFMkI1QTVSSUxTVyQlQCN0PWcu&rc715f3f34a484d8eae0a1809b30c6c5a=%22Feedback%22&r67e8aaf2389e4b9d8aa0c01902281211=9&rf690462c782847db972938cb1c773bc2=%22Easy%22&r781f1b5b16b14f5689b788fa6d98287b=%22Make%20a%20Charging%20Decision%22", - "REPORT_ISSUE_LINK": "https://cpsprod1.service-now.com/sp?id=sc_cat_item&sys_id=716b2e011bd66410d5b3dce3b24bcbbf", + "REPORT_ISSUE_LINK": "https://forms.office.com/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjJoWrYUO2RtKg-k_WWF_5qZUMFhXNTRVNkowWDhXSzFMOUc5SVhQQlVMSy4u", "ACCESSIBILITY_STATEMENT_URL": "https://cps-tst1.outsystemsenterprise.com/Casework/accessibility", "SHOW_MENU": true, "SHOW_RECENT_CASES": true, From c0dc736184f07b1910b2e38ef323686fbb59803b Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Mon, 13 Jul 2026 14:54:03 +0100 Subject: [PATCH 21/22] Housekeeping: switch prod accessibility URL --- configuration/config.prod.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/config.prod.json b/configuration/config.prod.json index a4913b799..69cf71cfe 100644 --- a/configuration/config.prod.json +++ b/configuration/config.prod.json @@ -18,7 +18,7 @@ "APP_INSIGHTS_CONNECTION_STRING": "InstrumentationKey=e572c03c-8d38-4771-b193-962f13da1b1a;IngestionEndpoint=https://polaris.cps.gov.uk/global-components/analytics/;ApplicationId=3dafc37d-8c9c-4480-90fc-532ac2b8bba2", "SURVEY_LINK": "https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjMYFiPHggxZNtgEAXRbAx2hUNjNWUDc0UEtVTERCMFpFMkI1QTVSSUxTVyQlQCN0PWcu&rc715f3f34a484d8eae0a1809b30c6c5a=%22Feedback%22&r67e8aaf2389e4b9d8aa0c01902281211=9&rf690462c782847db972938cb1c773bc2=%22Easy%22&r781f1b5b16b14f5689b788fa6d98287b=%22Make%20a%20Charging%20Decision%22", "REPORT_ISSUE_LINK": "https://forms.office.com/Pages/ResponsePage.aspx?id=HQ3dAObXOEOsUVZTOccIjJoWrYUO2RtKg-k_WWF_5qZUMFhXNTRVNkowWDhXSzFMOUc5SVhQQlVMSy4u", - "ACCESSIBILITY_STATEMENT_URL": "https://polaris.cps.gov.uk/global-components/prod/accessibility/statement.html", + "ACCESSIBILITY_STATEMENT_URL": "https://cps.outsystemsenterprise.com/Casework/accessibility", "SHOW_MENU": true, "SHOW_RECENT_CASES": true, "SHOW_MONITORING_CODES": false, From e4eeaca1dfd64e49c00012453cc9cf72df74398c Mon Sep 17 00:00:00 2001 From: Stef Stachow Date: Thu, 16 Jul 2026 15:18:26 +0100 Subject: [PATCH 22/22] Add interim-review.md --- infra/analytics/interim-review.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 infra/analytics/interim-review.md diff --git a/infra/analytics/interim-review.md b/infra/analytics/interim-review.md new file mode 100644 index 000000000..a580e6abd --- /dev/null +++ b/infra/analytics/interim-review.md @@ -0,0 +1,26 @@ +| Field | Can replicate | Notes | +| --------------------- | ------------- | ---------------------------------------------------- | +| TimeGenerated | Yes | Is this start or end time | +| Name | No | No - have a different naming system | +| UserId | ? | Not sure what this is - it this an OutSystems field? | +| UserAuthenticatedId | ? | Not sure what this is - is this an OutSystems field? | +| UserAccountId | ? | Not sure what this is | +| ApplicationName | Yes | | +| AreaID | Yes | | +| CMSArea | Yes | | +| CMSHomeUnit | Yes | | +| CMSHomeUnitID | Yes | | +| CaseID | Yes | | +| DateTime | ? | How does this relate to TimeGenerated? | +| EventType | Possibly | | +| IsCustomReview | No | | +| IsEAMTTaskCreated | No | | +| IsPEATaskCreated | No | | +| IsStartTask | ? | | +| IsSubsequentReview | Yes | | +| ReviewType | Yes | | +| TaskID | No | | +| TaskType | No | | +| TestType | ? | | +| TriggeringApplication | Yes | | +| URN | Yes | |