Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
316 changes: 316 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
name: Nightly Tests

on:
schedule:
- cron: '0 2 * * *' # Run at 2:00 AM UTC daily
workflow_dispatch: # Allow manual trigger

permissions:
contents: read
checks: write
actions: read

jobs:
lint:
name: Lint All Packages
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Enable Corepack
run: corepack enable

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24.11.1'

- name: Cache Yarn dependencies
uses: actions/cache@v4
with:
path: |
.yarn/cache
.yarn/install-state.gz
node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-

- name: Install dependencies
run: yarn install --immutable

- name: Setup Turbo cache
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-lint-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-lint-

- name: Run lint for all packages
run: npx turbo lint

test:
name: Unit & Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Enable Corepack
run: corepack enable

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24.11.1'

- name: Cache Yarn dependencies
uses: actions/cache@v4
with:
path: |
.yarn/cache
.yarn/install-state.gz
node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-

- name: Install dependencies
run: yarn install --immutable

- name: Generate Prisma client
run: yarn db:generate

- name: Setup Turbo cache
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-test-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-test-

- name: Run unit tests with coverage
run: npx turbo test -- --coverage

- name: Merge coverage reports
run: npx lcov-result-merger **/coverage/lcov.info lcov.info --prepend-source-files

- name: Upload coverage artifacts
uses: actions/upload-artifact@v5
if: always()
with:
name: unit-test-coverage-${{ github.run_id }}
path: 'lcov.info'
retention-days: 7
if-no-files-found: ignore

e2e:
name: E2E Tests (Including Nightly)
runs-on: ubuntu-latest
timeout-minutes: 30

services:
postgres:
image: postgres:18-alpine
env:
POSTGRES_USER: hmcts
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U hmcts -d postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 5
--health-start-period 10s

redis:
image: redis:8-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 5s
--health-retries 5
--health-start-period 10s

env:
# Application configuration
BASE_URL: https://localhost:8080
PORT: 8080
SESSION_SECRET: ${{ secrets.SESSION_SECRET }}

# Database Configuration (using service containers)
DATABASE_URL: postgresql://hmcts@localhost:5432/postgres
REDIS_URL: redis://localhost:6379

# SSO Configuration (using existing GitHub Secrets)
SSO_CLIENT_ID: ${{ secrets.SSO_CLIENT_ID }}
SSO_CLIENT_SECRET: ${{ secrets.SSO_CLIENT_SECRET }}
SSO_IDENTITY_METADATA: ${{ secrets.SSO_CONFIG_ENDPOINT }}
SSO_SYSTEM_ADMIN_GROUP_ID: ${{ secrets.SSO_SG_SYSTEM_ADMIN }}
SSO_INTERNAL_ADMIN_CTSC_GROUP_ID: ${{ secrets.SSO_SG_ADMIN_CTSC }}
SSO_INTERNAL_ADMIN_LOCAL_GROUP_ID: ${{ secrets.SSO_SG_ADMIN_LOCAL }}
SSO_ALLOW_HTTP_REDIRECT: false

# Test user credentials (using existing GitHub Secrets)
SSO_TEST_SYSTEM_ADMIN_EMAIL: ${{ secrets.SSO_TEST_SYSTEM_ADMIN_ACCOUNT_USER }}
SSO_TEST_SYSTEM_ADMIN_PASSWORD: ${{ secrets.SSO_TEST_SYSTEM_ADMIN_ACCOUNT_PWD }}
SSO_TEST_LOCAL_ADMIN_EMAIL: ${{ secrets.SSO_TEST_ADMIN_LOCAL_ACCOUNT_USER }}
SSO_TEST_LOCAL_ADMIN_PASSWORD: ${{ secrets.SSO_TEST_ADMIN_LOCAL_ACCOUNT_PWD }}
SSO_TEST_CTSC_ADMIN_EMAIL: ${{ secrets.SSO_TEST_ADMIN_ACCOUNT_CTSC_USER }}
SSO_TEST_CTSC_ADMIN_PASSWORD: ${{ secrets.SSO_TEST_ADMIN_ACCOUNT_CTSC_PWD }}
SSO_TEST_NO_ROLES_EMAIL: ${{ secrets.SSO_TEST_NO_ROLES_ACCOUNT_USER }}
SSO_TEST_NO_ROLES_PASSWORD: ${{ secrets.SSO_TEST_NO_ROLES_ACCOUNT_PWD }}

# CFT IDAM Configuration
ENABLE_CFT_IDAM: true
CFT_IDAM_URL: https://idam-web-public.aat.platform.hmcts.net
CFT_IDAM_CLIENT_SECRET: ${{ secrets.CFT_IDAM_CLIENT_SECRET }}

# CFT IDAM Test Credentials
CFT_VALID_TEST_ACCOUNT: ${{ secrets.CFT_VALID_TEST_ACCOUNT }}
CFT_VALID_TEST_ACCOUNT_PASSWORD: ${{ secrets.CFT_VALID_TEST_ACCOUNT_PASSWORD }}
CFT_INVALID_TEST_ACCOUNT: ${{ secrets.CFT_INVALID_TEST_ACCOUNT }}
CFT_INVALID_TEST_ACCOUNT_PASSWORD: ${{ secrets.CFT_INVALID_TEST_ACCOUNT_PASSWORD }}

# API Authentication Configuration (for blob ingestion endpoint)
AZURE_TENANT_ID: ${{ secrets.APP_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.APP_PIP_DATA_MANAGEMENT_ID }}
APP_PIP_DATA_MANAGEMENT_SCOPE: ${{ secrets.APP_PIP_DATA_MANAGEMENT_SCOPE }}

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Enable Corepack
run: corepack enable

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24.11.1'

- name: Cache Yarn dependencies
uses: actions/cache@v4
with:
path: |
.yarn/cache
.yarn/install-state.gz
node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-

- name: Install dependencies
run: yarn install --immutable

- name: Install PostgreSQL client
run: sudo apt-get update && sudo apt-get install -y postgresql-client

- name: Wait for PostgreSQL service to be ready
run: timeout 30 bash -c 'until pg_isready -h localhost -p 5432 -U hmcts; do sleep 1; done'

- name: Collate Prisma schema
run: |
echo "Collating Prisma schema from all modules..."
cd apps/postgres && npx tsx src/collate-schema.ts && cd ../..

- name: Generate Prisma client
run: yarn db:generate

- name: Run database migrations
run: yarn db:migrate

- name: Seed test database
run: |
echo "Running seed script..."
yarn workspace @hmcts/postgres run seed
echo "Seed script completed"

- name: Verify seed data exists
run: |
echo "Verifying seed data for locationId=9..."
ARTEFACT_COUNT=$(psql $DATABASE_URL -t -c "SELECT COUNT(*) FROM artefact WHERE location_id = '9';")
ARTEFACT_COUNT=$(echo $ARTEFACT_COUNT | xargs)
echo "Found $ARTEFACT_COUNT artefacts for locationId=9"
if [ "$ARTEFACT_COUNT" -lt 3 ]; then
echo "ERROR: Expected at least 3 seeded artefacts, found $ARTEFACT_COUNT"
exit 1
fi
echo "✓ Seed data verification successful"

- name: Build packages
run: yarn build

- name: Generate self-signed certificates for CI
run: |
chmod +x ./scripts/generate-ci-certs.sh
./scripts/generate-ci-certs.sh

- name: Restore Playwright browsers cache
uses: actions/cache/restore@v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('e2e-tests/package.json') }}
restore-keys: |
${{ runner.os }}-playwright-

- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn workspace e2e-tests run playwright install chromium --with-deps

- name: Install Playwright system dependencies (if cached)
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: yarn workspace e2e-tests run playwright install-deps chromium

- name: Save Playwright browsers cache
if: steps.playwright-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('e2e-tests/package.json') }}

- name: Run E2E tests (including nightly)
id: e2e-tests
run: |
# Capture all output including server logs
set -o pipefail
yarn workspace e2e-tests run test:e2e:all 2>&1 | tee e2e-server-logs.txt

- name: Test Report
id: test-report
uses: dorny/test-reporter@v2
if: always()
with:
name: Nightly E2E Test Results
path: e2e-tests/junit-results.xml
reporter: java-junit
fail-on-error: false

- name: Upload Playwright test results
uses: actions/upload-artifact@v5
if: always()
with:
name: playwright-test-results-${{ github.run_id }}
path: |
e2e-tests/test-results/
e2e-tests/playwright-report/
retention-days: 7

- name: Upload application logs
uses: actions/upload-artifact@v5
if: always()
with:
name: application-logs-${{ github.run_id }}
path: e2e-server-logs.txt
retention-days: 7
66 changes: 66 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,72 @@ describe('UserService', () => {
});
```

### E2E Testing with Playwright

**Test Organization:**
- Location: `e2e-tests/`
- Naming: `*.spec.ts`
- Tag nightly-only tests with `@nightly` in test title

**Test Patterns (Sequential in Each Test):**
1. Test content and functionality
2. Test Welsh translation (same journey)
3. Test accessibility inline (not separate)
4. Test keyboard navigation
5. Test responsive behavior

**Example Pattern:**
```typescript
test('user can complete journey @nightly', async ({ page }) => {
// 1. Test main journey
await page.goto('/start');
await page.getByRole('button', { name: 'Start now' }).click();

// 2. Test Welsh
await page.getByRole('link', { name: 'Cymraeg' }).click();
expect(await page.getByRole('heading', { level: 1 })).toContainText('Dechrau nawr');

// 3. Test accessibility inline
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
expect(accessibilityScanResults.violations).toEqual([]);

// 4. Test keyboard navigation
await page.keyboard.press('Tab');
await page.keyboard.press('Enter');

// 5. Continue journey...
});
```

**Correct Selectors (Priority Order):**
1. `getByRole()` - Preferred for accessibility
2. `getByLabel()` - For form inputs
3. `getByText()` - For specific text
4. `getByTestId()` - Last resort only

**DO NOT Test:**
- Font sizes
- Background colors
- Margins/padding
- Any visual styling
- UI design aspects

**Test Data Management:**
- Use global-setup.ts for reference data seeding
- Use test-specific data creation in tests
- Clean up test data in global-teardown.ts

**Coverage Expectations:**
- Business logic: >80%
- E2E tests: Cover critical user journeys
- Accessibility: Test inline with journeys (not separately)

**Running Tests:**
```bash
yarn test:e2e # Run E2E tests (excludes @nightly)
yarn test:e2e:all # Run all E2E tests (including @nightly)
```

## Code Quality Standards

- **TypeScript**: Strict mode enabled, no `any` without justification
Expand Down
Loading
Loading