Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cb32393
Configure Saucelab
SameenaHMCTS Mar 19, 2026
3530032
Configure Saucelab
SameenaHMCTS Mar 19, 2026
2946377
Configure Saucelab
SameenaHMCTS Mar 19, 2026
f7712ef
Configure Saucelab - simplified
SameenaHMCTS Mar 19, 2026
f4db685
Removed API invocation
SameenaHMCTS Mar 20, 2026
9162cde
Reusing s2s and idam from prev step
SameenaHMCTS Mar 20, 2026
f4b257a
enabled user creation
SameenaHMCTS Mar 20, 2026
3671f2a
Failing moke test stage
SameenaHMCTS Mar 20, 2026
8d4eb05
reverted Failing moke test stage
SameenaHMCTS Mar 20, 2026
2c0fd30
Merge branch 'master' into HDPI-5349-Saucelabs-integration-POC
SameenaHMCTS Mar 20, 2026
ee6ffc4
refactored the code
SameenaHMCTS Mar 22, 2026
352eefc
Removed afteralway
SameenaHMCTS Mar 23, 2026
17f149f
Added report to sauce ignore
SameenaHMCTS Mar 23, 2026
cabf2e7
Added report to sauce ignore
SameenaHMCTS Mar 23, 2026
b7de8cf
Add hybrid Sauce Grid cross-browser (local Playwright + remote Chrome)
SameenaHMCTS Mar 23, 2026
20393c0
Merge origin/nightly-dev; resolve Sauce crossbrowser + global-setup +…
SameenaHMCTS Mar 23, 2026
a5fc058
fix: restore saucectl runner with nightly-dev S2S/Idam + loadSauceEnv…
SameenaHMCTS Mar 23, 2026
504361e
Added report to sauce ignore
SameenaHMCTS Mar 23, 2026
2f166c6
chore: drop cnp-jenkins-library-hdpi from .sauceignore (local clone n…
SameenaHMCTS Mar 23, 2026
76688b5
Added report to sauce ignore
SameenaHMCTS Mar 23, 2026
3af1ba0
Added selenium grid for api access
SameenaHMCTS Mar 23, 2026
a41b2c4
Added selenium grid for api access
SameenaHMCTS Mar 23, 2026
2d63101
Merge branch 'master' into HDPI-5349-Saucelabs-integration-POC
SameenaHMCTS Mar 31, 2026
88bf6b7
Removed tokens from display
SameenaHMCTS Mar 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .sauce/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Entry: yarn test:crossbrowserui (alias: yarn test:crossbrowser) → saucectl run.
apiVersion: v1alpha
kind: playwright

sauce:
region: eu-central-1
concurrency: 1
retries: 0
metadata:
tags:
- pcs-frontend
- crossbrowser

playwright:
version: '1.58.1'
configFile: playwright.sauce.config.ts
nodeVersion: 'v20'
rootDir: ./

npm:
registries:
- url: https://registry.npmjs.org
usePackageLock: false

# $VAR expanded by saucectl from the agent environment (e.g. HTTP_PROXY / HTTPS_PROXY from the shell).
env:
CI: 'true'
TEST_URL: '$TEST_URL'
NPM_CONFIG_PRODUCTION: 'false'
PCS_FRONTEND_IDAM_SECRET: '$PCS_FRONTEND_IDAM_SECRET'
IDAM_PCS_USER_EMAIL: '$IDAM_PCS_USER_EMAIL'
IDAM_PCS_USER_PASSWORD: '$IDAM_PCS_USER_PASSWORD'

# Full matrix: Chrome/Firefox × Windows/macOS (nightly). Comment out suites locally if you need a quicker run.
suites:
- name: PCS crossbrowser chrome windows
platformName: 'Windows 10'
screenResolution: '1440x900'
preExec:
# Sauce caps all preExec at 300s total; full install often needs --no-audit/--ignore-scripts (skip husky prepare).
- npm install --include=dev --no-audit --no-fund --ignore-scripts --loglevel=error
- npx ts-node --transpile-only ./scripts/sauce-remove-playwright.ts
testMatch: [".*e2eTest/.*\\.spec\\.ts"]
passThreshold: 1
params:
browserName: 'chromium'
project: 'chromium'
grep: '@crossbrowser'

# - name: PCS crossbrowser chrome mac
# platformName: "macOS 13"
# screenResolution: "1440x900"
# preExec:
# - npm install --include=dev --no-audit --no-fund --ignore-scripts --loglevel=error
# - npx ts-node --transpile-only ./scripts/sauce-remove-playwright.ts
# testMatch: [".*e2eTest/pageNotFound\\.spec\\.ts"]
# passThreshold: 1
# params:
# browserName: "chromium"
# project: "chromium"
# grep: "@crossbrowser"
#
# - name: PCS crossbrowser firefox windows
# platformName: "Windows 10"
# screenResolution: "1440x900"
# preExec:
# - npm install --include=dev --no-audit --no-fund --ignore-scripts --loglevel=error
# - npx ts-node --transpile-only ./scripts/sauce-remove-playwright.ts
# testMatch: [".*e2eTest/pageNotFound\\.spec\\.ts"]
# passThreshold: 1
# params:
# browserName: "firefox"
# project: "firefox"
# grep: "@crossbrowser"
#
# - name: PCS crossbrowser firefox mac
# platformName: "macOS 13"
# screenResolution: "1440x900"
# preExec:
# - npm install --include=dev --no-audit --no-fund --ignore-scripts --loglevel=error
# - npx ts-node --transpile-only ./scripts/sauce-remove-playwright.ts
# testMatch: [".*e2eTest/pageNotFound\\.spec\\.ts"]
# passThreshold: 1
# params:
# browserName: "firefox"
# project: "firefox"
# grep: "@crossbrowser"
40 changes: 40 additions & 0 deletions .sauceignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Dependencies – preExec runs npm install on the Sauce VM; do not upload local installs
node_modules/
.yarn/cache/
.yarn/unplugged/
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# Build and output
dist/
public/
coverage/
.nyc_output/
*.css

# Git and IDE
.git/
.idea/
*.iml
.vscode/
.DS_Store

# Test reports & local outputs
allure-report/
allure-results/
playwright-report/
test-results/
blob-report/
playwright/.cache/
functional-output/
smoke-output/
pact/

# Java / Gradle (not needed for Playwright suite)
src/test/java/build/
src/test/java/.gradle/

# Secrets
.env
.env.*
12 changes: 8 additions & 4 deletions Jenkinsfile_nightly
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!groovy

properties([
// H allow predefined but random minute see https://en.wikipedia.org/wiki/Cron#Non-standard_characters
pipelineTriggers([cron('H 07 * * 1-5')]),
parameters([
choice(
Expand Down Expand Up @@ -33,7 +32,7 @@ properties([
name: 'ACCESSIBILITY_TESTS',
defaultValue: true,
description: 'Tick the checkbox to run Accessibility tests.'
),
)
])
])

Expand Down Expand Up @@ -62,11 +61,16 @@ def secrets = [

withNightlyPipeline(type, product, component) {
validateInputs()
enableSlackNotifications('#pcs-tech')
enableSlackNotifications('#qa-pipeline-status')
loadVaultSecrets(secrets)
handleEnvironmentSetting()
setFunctionalTestEnvVars()
enableFortifyScan()
enableCrossBrowserTest(120)

afterAlways('crossBrowserTest') {
steps.archiveArtifacts allowEmptyArchive: true, artifacts: '**/test-results/**,**/playwright-report/**,.sauce/**'
}

afterAlways('fortify-scan') {
steps.archiveArtifacts allowEmptyArchive: true, artifacts: '**/Fortify Scan/**/*'
Expand Down Expand Up @@ -163,7 +167,7 @@ def sendAllureReportToSlack(String browser, String reportSuffix) {
def msg = sh(script: "yarn exec ts-node src/test/ui/scripts/allure-slack-notifier.ts --print-only --pipeline-type nightly 2>/dev/null || true", returnStdout: true).trim()
if (!msg) { msg = "E2E Test Results — Build #${env.BUILD_NUMBER} — See ${env.BUILD_URL} for details" }
try {
slackSend(channel: '#hdp-qa-e2e-test-results', message: msg)
slackSend(channel: '#qa-pipeline-status', message: msg)
} catch (Exception e) {
echo "WARNING: Slack notification failed: ${e.message}"
}
Expand Down
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,19 @@ Running accessibility tests:
yarn test:accessibility
```

#### Sauce Labs (cross-browser)

| Script | What it does |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`yarn test:crossbrowserui`** | [saucectl](https://docs.saucelabs.com/dev/cli/saucectl/) — tests and browser run **on Sauce** per **`.sauce/config.yml`**. Runner mints S2S/Idam on the agent first; see **`docs/sauce-jenkins.md`**. |
| **`yarn test:crossbrowser`** | Same as **`test:crossbrowserui`** (alias for Jenkins / older docs). |
| **`yarn test:crossbrowsergrid`** | **Hybrid:** Playwright + APIs on **your machine or Jenkins**; only **Chrome** on Sauce via Selenium Grid ([docs](https://docs.saucelabs.com/web-apps/automated-testing/playwright/selenium-grid/)). Set **`SAUCE_USERNAME`**, **`SAUCE_ACCESS_KEY`**, optional **`SAUCE_TUNNEL_*`**. |
| **`yarn test:crossbrowserlocal`** | **No Sauce** — local Chromium, **`@crossbrowser`** specs only. |

**saucectl (`test:crossbrowserui`):** install deps so `saucectl` exists (`yarn install`), export Sauce + tunnel vars from [user settings](https://app.saucelabs.com/user-settings), start **Sauce Connect** with a tunnel id that matches **`SAUCE_TUNNEL_NAME`**, then run **`yarn test:crossbrowserui`**. Set **`HTTP_PROXY` / `HTTPS_PROXY`** in the shell if your network requires them.

More detail: **`docs/sauce-jenkins.md`** and **`src/test/ui/test-README.md`**.

### Security

#### CSRF prevention
Expand Down Expand Up @@ -209,12 +222,14 @@ There is a configuration section related with those headers, where you can speci

- `referrerPolicy` - value of the `Referrer-Policy` header

Here's an example setup:
Here's an example setup (JSON):

```json
"security": {
"referrerPolicy": "origin",
}
```
{
"security": {
"referrerPolicy": "origin"
}
}
```

Make sure you have those values set correctly for your application.
Expand Down
54 changes: 54 additions & 0 deletions docs/sauce-jenkins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Sauce cross-browser: Jenkins vs local

**Pipeline:** `@Jenkinsfile_nightly`

## Jenkins (CNP common library)

| Piece | What happens |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **When** | Nightly pipeline: **`enableCrossBrowserTest(120)`** in **`Jenkinsfile_nightly`**. |
| **Sauce credentials** | Jenkins **`reform_tunnel`** credential + Sauce plugin → **`SAUCE_USERNAME`** / **`SAUCE_ACCESS_KEY`** on the agent. You do **not** paste Sauce keys into the repo or extra vault entries for Sauce. |
| **Sauce Connect** | **`withSauceConnect('reform_tunnel')`** in **`hmcts/cnp-jenkins-library`** starts the tunnel (**`reformtunnel`**, shared pool). You do **not** run **`sc`** yourself on the agent. |
| **Tunnel in `saucectl`** | **`runSauceCrossbrowser.ts`** sees Jenkins (`BUILD_TAG` / `JENKINS_URL`) and defaults **`reformtunnel`** + **`SAUCE_USERNAME`** if **`SAUCE_TUNNEL_*`** are unset — aligned with the library. |
| **Idam / URL** | **`loadVaultSecrets`** + **`handleEnvironmentSetting()`** set **`PCS_FRONTEND_IDAM_SECRET`**, **`IDAM_PCS_USER_PASSWORD`**, **`TEST_URL`** on the agent. **`.sauce/config.yml`** also passes **`IDAM_PCS_USER_EMAIL`** when you use a **fixed** Idam user (e.g. **`createUser` commented out** in a spec); otherwise **`createUser`** sets a random email on the VM. |

## Local (your machine)

| Piece | What you do |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sauce Connect** | Start **`sc`** yourself (same tunnel id you will pass to **`saucectl`**). |
| **Tunnel env** | Export **`SAUCE_TUNNEL_NAME`** and **`SAUCE_TUNNEL_OWNER`** (match **`sc -i`** / Sauce UI). Jenkins defaults do **not** apply when **`BUILD_TAG`** / **`JENKINS_URL`** are unset. |
| **Sauce API creds** | Export **`SAUCE_USERNAME`** and **`SAUCE_ACCESS_KEY`** (your Sauce account). |
| **Idam / app** | Export **`PCS_FRONTEND_IDAM_SECRET`** and **`IDAM_PCS_USER_PASSWORD`**. If you **skip `createUser`** and log in as a fixed user, also export **`IDAM_PCS_USER_EMAIL`** (it is forwarded in **`.sauce/config.yml`**). Optionally **`TEST_URL`**. |
| **Run** | `yarn test:crossbrowserui` or `yarn test:crossbrowser` (alias; tunnel already up). Set proxy env in the shell if required. |

---

## What runs (both environments)

**`yarn test:crossbrowserui`** (or **`yarn test:crossbrowser`**) → **`scripts/crossbrowser/runSauceCrossbrowser.ts`** → **`saucectl run`** (all suites in **`.sauce/config.yml`**). Suite **`params`** (e.g. **`grep`**) and **`testMatch`** are defined in the YAML.

**Hybrid:** **`yarn test:crossbrowsergrid`** → **`playwright.saucegrid.config.ts`** — Playwright + APIs on the agent; remote Chrome on Sauce (Selenium Grid).

## How env reaches the Sauce VM

**`saucectl`** expands **`"$VAR"`** in **`.sauce/config.yml`** from the **current process environment** (Jenkins agent or your shell). No **`saucectl -e`** list in code for Idam.

## S2S

**`S2S_SECRET`** is not passed through **`.sauce/config.yml`**. On the **Jenkins agent**, **`runSauceCrossbrowser.ts`** obtains **`SERVICE_AUTH_TOKEN`** (and Idam **`BEARER_TOKEN`**) before **`saucectl`**, and forwards them via **`.sauce/config.yml`**. On the **Sauce VM**, **`global-setup.config.ts`** skips those network calls when the tokens are already set (same pattern as local Playwright when tokens are not pre-set).

## Pre-exec timeout (300s)

Sauce applies a **5-minute total limit** for **`preExec`** (see Sauce Playwright YAML docs). A full **`npm install`** can exceed that; **`.sauce/config.yml`** uses **`--no-audit --no-fund --ignore-scripts`** to reduce time. If it still times out, consider a committed **`package-lock.json`** + **`npm ci`**, or ask Platform / Sauce about options.

## Nightly full matrix

- **`.sauce/config.yml`** lists **four suites** (Chrome/Firefox × Windows/macOS). **`sauce.concurrency: 1`** runs them **one after another** (same `saucectl run`).
- **`Jenkinsfile_nightly`** already calls **`enableCrossBrowserTest(120)`** (timeout in **minutes** per CNP). If the job hits the limit with four suites, **raise the number** (e.g. `180`) after checking with Platform.
- **Vault**: **`PCS_FRONTEND_IDAM_SECRET`** and **`IDAM_PCS_USER_PASSWORD`** are loaded. Add **`IDAM_PCS_USER_EMAIL`** to vault + **`loadVaultSecrets`** only if your specs use a **fixed** Idam user (no **`createUser`**). Otherwise **`createUser`** sets email on the VM.

## Artefacts (Jenkins)

- Library **`crossBrowserTest()`**: **`functional-output/crossbrowser/reports/**/\*`**, **`saucePublisher()`\*\*.
- **`afterAlways('crossBrowserTest')`**: **`test-results`**, **`playwright-report`**, **`.sauce`**.
24 changes: 24 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const compat = new FlatCompat({
module.exports = defineConfig([
{
files: ['**/*.ts', '**/*.tsx'],
ignores: ['scripts/**'],
languageOptions: {
globals: {
...globals.browser,
Expand Down Expand Up @@ -139,6 +140,29 @@ module.exports = defineConfig([
],
},
},
{
files: ['scripts/**/*.ts', 'playwright.sauce.config.ts', 'playwright.saucegrid.config.ts'],
languageOptions: {
parser: tsParser,
ecmaVersion: 2018,
sourceType: 'module',
parserOptions: {
project: './tsconfig.scripts.json',
tsconfigRootDir: __dirname,
},
},
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: './tsconfig.scripts.json',
},
},
},
rules: {
'no-console': 'off',
},
},
{
files: ['src/test/ui/**/*.ts'],
rules: {
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
"test:pact:run-and-publish": "yarn test:pact && yarn pact:publish",
"test:can-i-deploy:consumer": "pact-broker can-i-deploy --pacticipant 'pcs_frontend' --version $(git rev-parse --short HEAD) --broker-base-url https://pact-broker.platform.hmcts.net",
"test:openAllureReport": "yarn allure open allure-report",
"test:crossbrowser": "yarn test:crossbrowsersaucecntrl",
"test:crossbrowsersaucecntrl": "yarn exec ts-node --transpile-only scripts/crossbrowser/runSauceCrossbrowser.ts",
"test:crossbrowsergrid": "bash scripts/crossbrowser/run-sauce-grid.sh",
"test:crossbrowserlocal": "yarn playwright install && yarn playwright test --project chrome --grep @crossbrowser; EXIT_CODE=$?; allure generate --clean; ts-node src/test/ui/config/clean-attachments.config.ts; exit $EXIT_CODE",
"fortifyScan": "./src/test/java/gradlew -p src/test/java fortifyScan",
"prepare": "husky"
},
Expand Down Expand Up @@ -144,6 +148,7 @@
"prettier": "^3.0.0",
"sass": "^1.65.1",
"sass-loader": "^16.0.0",
"saucectl": "^0.202.0",
"style-loader": "^4.0.0",
"stylelint": "^16.0.0",
"stylelint-config-standard": "^38.0.0",
Expand Down
24 changes: 12 additions & 12 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export default defineConfig({
use: {
...devices['Desktop Chrome'],
channel: 'chrome',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
screenshot: 'on',
video: 'on',
trace: 'on-first-retry',
javaScriptEnabled: true,
viewport: DEFAULT_VIEWPORT,
Expand All @@ -69,8 +69,8 @@ export default defineConfig({
use: {
...devices['Desktop Firefox'],
channel: 'firefox',
screenshot: 'only-on-failure' as const,
video: 'retain-on-failure' as const,
screenshot: 'on' as const,
video: 'on' as const,
trace: 'on-first-retry' as const,
javaScriptEnabled: true,
viewport: DEFAULT_VIEWPORT,
Expand All @@ -82,8 +82,8 @@ export default defineConfig({
use: {
...devices['Desktop Safari'],
channel: 'webkit',
screenshot: 'only-on-failure' as const,
video: 'retain-on-failure' as const,
screenshot: 'on' as const,
video: 'on' as const,
trace: 'on-first-retry' as const,
javaScriptEnabled: true,
viewport: DEFAULT_VIEWPORT,
Expand All @@ -95,8 +95,8 @@ export default defineConfig({
use: {
...devices['Pixel 5'],
channel: 'MobileChrome',
screenshot: 'only-on-failure' as const,
video: 'retain-on-failure' as const,
screenshot: 'on' as const,
video: 'on' as const,
trace: 'on-first-retry' as const,
javaScriptEnabled: true,
viewport: DEFAULT_VIEWPORT,
Expand All @@ -108,8 +108,8 @@ export default defineConfig({
use: {
...devices['iPhone 12'],
channel: 'MobileSafari',
screenshot: 'only-on-failure' as const,
video: 'retain-on-failure' as const,
screenshot: 'on' as const,
video: 'on' as const,
trace: 'on-first-retry' as const,
javaScriptEnabled: true,
viewport: DEFAULT_VIEWPORT,
Expand All @@ -121,8 +121,8 @@ export default defineConfig({
use: {
...devices['Desktop Edge'],
channel: 'MicrosoftEdge',
screenshot: 'only-on-failure' as const,
video: 'retain-on-failure' as const,
screenshot: 'on' as const,
video: 'on' as const,
trace: 'on-first-retry' as const,
javaScriptEnabled: true,
viewport: DEFAULT_VIEWPORT,
Expand Down
Loading
Loading