Skip to main content
Version: 1.x

Docker & CI/CD

Praman's CI/CD pipeline runs on GitHub Actions with a 3-OS matrix, Docker support for containerized test execution, and comprehensive quality gates.

GitHub Actions CI Pipeline

The CI workflow (.github/workflows/ci.yml) runs on every push to main and every pull request. It consists of 6 parallel jobs:

Job Overview

+--------------------+     +-------------------------+     +-----------------+
| quality | | unit-tests | | build |
| (lint, typecheck, | | (3 OS x 3 Node) | | (3 OS) |
| spell, deadcode, | | ubuntu/windows/macos | | ESM + CJS + DTS |
| markdown lint) | | Node 20/22/24 | | attw, size-limit|
+--------------------+ +-------------------------+ +-----------------+
| |
+-------------------------+-------------------------------+
|
+--------------v--------------+
| integration-tests |
| (PW 1.57.0, PW 1.58.2) |
| SAP cloud auth via secrets |
+-----------------------------+

+--------------------+ +-------------------------+
| security | | docs-check |
| (npm audit) | | (TypeDoc + Docusaurus) |
+--------------------+ +-------------------------+

3-OS Matrix (Unit Tests)

Unit tests run across a full OS and Node.js matrix:

strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [20, 22, 24]

This produces 9 test runs (3 OS x 3 Node versions) to catch platform-specific issues early.

Quality Job

- run: npm run lint # ESLint with 11 plugins, 0 errors/warnings
- run: npm run typecheck # tsc --noEmit (strict mode)
- run: npx cspell "src/**/*.ts" "docs/**/*.md" --no-progress # Spell check
- run: npx knip # Dead code detection
- run: npx markdownlint-cli2 "**/*.md" # Markdown lint

Build Job

- run: npm run build # tsup: ESM + CJS + DTS
- run: node -e "const m = require('./dist/index.cjs'); ..." # CJS smoke test
- run: npx size-limit # Bundle size check
- run: npm run check:exports # attw: validate all 6 export maps

Integration Tests

Integration tests run against SAP cloud systems with Playwright version matrix:

strategy:
matrix:
playwright-version: ['1.57.0', '1.58.2']
env:
SAP_CLOUD_BASE_URL: ${{ secrets.SAP_CLOUD_BASE_URL }}
SAP_CLOUD_USERNAME: ${{ secrets.SAP_CLOUD_USERNAME }}
SAP_CLOUD_PASSWORD: ${{ secrets.SAP_CLOUD_PASSWORD }}

Security Scan

- run: npm audit --audit-level=high

Documentation Validation

- run: npx typedoc --validation # TypeDoc API docs
- run: npm run build -- --no-minify # Docusaurus site build (in docs/)

Azure Playwright Workspaces (Optional)

Triggered manually or by adding the azure-test label to a PR:

- run: npx playwright test --config=playwright.service.config.ts --workers=20
env:
PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }}

npm run ci Breakdown

The local npm run ci command chains all quality gates:

npm run ci
# Equivalent to:
# npm run lint && npm run typecheck && npm run test:unit && npm run build
StepCommandPurpose
Linteslint src/ tests/ --max-warnings=011 ESLint plugins, zero tolerance
Typechecktsc --noEmitTypeScript strict mode validation
Unit testsvitest runHermetic unit tests with Vitest
BuildtsupDual ESM + CJS output with type declarations

Additional quality commands available:

CommandPurpose
npm run test:unit -- --coverageUnit tests with V8 coverage
npm run check:exportsValidate all 6 sub-path exports with attw
npm run format:checkPrettier formatting check
npm run spellcheckCSpell spell checking
npm run deadcodeKnip dead code detection
npm run mdlintMarkdown lint

Docker for Running Tests

Dockerfile for Test Execution

Create a Dockerfile for running Praman tests in a container:

FROM mcr.microsoft.com/playwright:v1.57.0-noble

WORKDIR /app

# Copy package files and install
COPY package.json package-lock.json ./
RUN npm ci

# Copy source and test files
COPY . .

# Build the project
RUN npm run build

# Install Playwright browsers
RUN npx playwright install --with-deps chromium

# Default: run all tests
CMD ["npm", "run", "test:integration"]

Running Tests in Docker

# Build the test image
docker build -t praman-tests .

# Run integration tests
docker run --rm \
-e SAP_CLOUD_BASE_URL="https://your-sap-system.example.com" \
-e SAP_CLOUD_USERNAME="testuser" \
-e SAP_CLOUD_PASSWORD="secret" \
praman-tests

# Run only unit tests
docker run --rm praman-tests npm run test:unit

# Run with coverage
docker run --rm -v $(pwd)/coverage:/app/coverage \
praman-tests npm run test:unit -- --coverage

Docker Compose for Local SAP Mock

For local development with a mock SAP backend:

# docker-compose.yml
services:
sap-mock:
image: node:20-slim
working_dir: /app
volumes:
- ./tests/mocks/odata-server:/app
command: node server.js
ports:
- '4004:4004'
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:4004/health']
interval: 5s
timeout: 3s
retries: 5

tests:
build: .
depends_on:
sap-mock:
condition: service_healthy
environment:
- SAP_CLOUD_BASE_URL=http://sap-mock:4004
- SAP_CLOUD_USERNAME=mock-user
- SAP_CLOUD_PASSWORD=mock-pass
volumes:
- ./playwright-report:/app/playwright-report
- ./test-results:/app/test-results

Run the full stack:

# Start mock server and run tests
docker compose up --build --abort-on-container-exit

# View test results
open playwright-report/index.html

Security Practices in CI

All GitHub Actions use SHA-pinned action references:

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4

Additional security measures:

  • permissions: contents: read (minimal permissions)
  • concurrency groups prevent duplicate runs
  • cancel-in-progress: true for PRs
  • SAP credentials stored as GitHub Secrets, never in code
  • npm audit --audit-level=high in every CI run

Coverage Reporting

Coverage artifacts are uploaded per-matrix entry for comparison:

- uses: actions/upload-artifact
with:
name: coverage-${{ matrix.os }}-node-${{ matrix.node-version }}
path: coverage/

Coverage thresholds are enforced per-file (not just project-wide):

TierScopeStatementsBranches
Tier 1Error classes100%100%
Tier 2Core infrastructure95%90%
Tier 3All other modules90%85%

Praman-Specific Azure Playwright Configuration

When running Praman tests with Azure Playwright Testing, the bridge must communicate with cloud-hosted browsers. This works out of the box because the bridge uses standard page.evaluate() calls that serialize across remote browser connections.

Service Configuration

Create a playwright.service.config.ts that extends your base config:

import { defineConfig } from '@playwright/test';
import { getServiceConfig } from '@azure/microsoft-playwright-testing';
import baseConfig from './playwright.config';

export default defineConfig(baseConfig, getServiceConfig(baseConfig), {
workers: 20,
use: {
// Increase timeouts for cloud browser latency
actionTimeout: 15_000,
navigationTimeout: 60_000,
},
expect: {
timeout: 10_000,
},
});

Environment Variables

Set these in your CI pipeline or .env file:

PLAYWRIGHT_SERVICE_URL=wss://eastus.api.playwright.microsoft.com/...
PLAYWRIGHT_SERVICE_ACCESS_TOKEN=<your-token>
# Praman bridge timeout (higher for cloud latency)
PRAMAN_CONTROL_DISCOVERY_TIMEOUT=45000

Worker and Timeout Considerations

Azure Playwright Testing supports up to 50 parallel workers. Keep these points in mind:

  • SAP session limits: Your SAP system may restrict concurrent sessions per user. Use separate test accounts per worker shard, or reduce workers to match your license.
  • Bridge injection latency: Cloud browsers add 50-200ms round-trip overhead per page.evaluate() call. Increase controlDiscoveryTimeout to 45 seconds or higher.
  • Auth state reuse: Use Playwright's storageState to persist authentication and avoid re-login on every worker. This is especially important at scale because SAP IdP rate-limits login attempts.

Running in CI

- name: Run Praman tests on Azure Playwright
run: npx playwright test --config=playwright.service.config.ts
env:
PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }}
PLAYWRIGHT_SERVICE_ACCESS_TOKEN: ${{ secrets.PLAYWRIGHT_SERVICE_ACCESS_TOKEN }}
SAP_CLOUD_BASE_URL: ${{ secrets.SAP_CLOUD_BASE_URL }}
PRAMAN_CONTROL_DISCOVERY_TIMEOUT: '45000'