Architecture
Approach for SAP S/4 HANA Test Automation
Aligned to SAP Activate. Quality automation starts in Realize.
How It Works
How Playwright + Praman Plugin Works: End-to-End Flow
From seed file authentication through live SAP discovery to compliant test artifacts — fully orchestrated by AI agents.
Internal Architecture
End-to-End Orchestration
Test code → Fixtures → Bridge → UI5 Control Resolution → Smart Waits & Retries → Assertions.
Traced from the real BOM E2E Gold Standard test through 6 architectural layers.
Technical Reference
Architecture Deep Dive
Comprehensive technical documentation verified against source code. 180 source files, 551 type definitions, 6-layer architecture — every number counted, not estimated.
Metrics at a Glance
All numbers verified against source code. No estimates — every metric is counted from the actual codebase.
Testing & Coverage
Core Architecture Layers
Praman follows a strict 6-layer architecture. Lower layers never import from higher layers. Each layer has a well-defined responsibility and communicates only with its adjacent layers.
Foundation layer: 13 typed error classes extending PramanError with structured code, attempted, retryable, and suggestions[] fields. Config system using Readonly<PramanConfig>. Pino-based structured logging (no console.log). Wait helpers: waitForUI5Stable(), briefDOMSettle(), waitForUI5Bootstrap(). 551 type definitions (389 interfaces + 162 type aliases).
10 browser scripts injected via page.evaluate(). Idempotent injection using WeakSet — ensureBridgeInjected(page) sets up window.__praman_bridge with objectMap, getById(), and 3-tier control lookup. 3 interaction strategies: UI5Native (firePress/fireSelect/fireTap fallback chain), DomFirst (DOM click first), OPA5 (RecordReplay-based).
ES Proxy with 8-step get trap resolution. 13 built-in method overrides (getId, getControlType, press, enterText, select, exec, getAggregation, toString, toJSON, renewWebElementReference, getControlMetadata, getControlInfoFull, retrieveMembers). 9-type return classification system. Fluent chaining via createChainableResult(). Blacklist enforcement prevents calling dangerous methods.
11 fixture modules composed via Playwright's mergeTests(): module, auth, nav, stability, fe, ai, intent, shellFooter, flpLocks, flpSettings, testData. 10 custom UI5 matchers: checkUI5Text, checkUI5Visible, checkUI5Enabled, checkUI5Property, checkUI5ValueState, checkUI5Binding, checkUI5ControlType, checkUI5RowCount, checkUI5CellText, checkUI5SelectedRows.
AI integration with optional dependencies (@anthropic-ai/sdk, openai). Intent-based test generation, vocabulary system for SAP domain terms, AI-powered test healing. Exports via sub-paths: playwright-praman/ai, playwright-praman/intents, playwright-praman/vocabulary.
Core Design Principles
| Principle | Implementation | Enforcement |
|---|---|---|
| Strict TypeScript | No `any`, no `as unknown as T`. 551 types, exactOptionalPropertyTypes, verbatimModuleSyntax | typescript-eslint strict type-checked |
| Immutable Config | Readonly<PramanConfig> — never mutated at runtime | TypeScript compiler + code review |
| Structured Errors | 14 error classes with code, attempted, retryable, suggestions[] | PramanError base class enforcement |
| No console.log | Pino structured logging only | ESLint rule + code review |
| No page.waitForTimeout() | Smart waits: waitForUI5Stable(), briefDOMSettle() | eslint-plugin-playwright ban list |
| ESM-first | import/export only, node: prefix, .js extensions | eslint-plugin-n, eslint-plugin-import-x |
| Layer Isolation | Lower layers never import higher layers | eslint-plugin-import-x boundaries |
| Web-first Assertions | Playwright expect + custom UI5 matchers | eslint-plugin-playwright |
| Cross-platform | node:path, node:fs/promises, no hardcoded separators | CI 3-OS matrix (Linux, macOS, Windows) |
| Per-file Coverage | Tier 1: 100%, Tier 2: 95%, Tier 3: 90% — no file hides behind averages | @vitest/coverage-v8 perFile: true |
| TSDoc Documentation | Microsoft TSDoc standard, custom tags (@intent, @guarantee, @capability) | eslint-plugin-tsdoc with syntax: error |
| Security First | @microsoft/eslint-plugin-sdl, eslint-plugin-security, SHA-pinned Actions | 11 ESLint plugins, zero tolerance |
Data Flow
The data flow traces a single UI5 interaction from test code through all 5 architecture layers and back. Non-serializable objects are stored in the bridge's objectMap with UUID keys for later retrieval.
Flow Steps
- Test Code calls fixture method (e.g., ui5.control(selector))
- Fixtures ensure bridge is injected (idempotent via WeakSet), then invoke bridge
- Bridge Injection sets up window.__praman_bridge with objectMap + getById()
- Browser Context executes 3-tier control discovery in the live SAP application
- Control Proxy wraps the discovered control with ES Proxy for method interception
- page.evaluate() routes each method call through bridge scripts to the UI5 runtime
- Return Handler classifies the result into one of 9 types, creates sub-proxies for elements
- Assertions verify the result using Playwright expect + 10 custom UI5 matchers
Directory Structure
| Path | Description |
|---|---|
| src/ai/ | AI integration (Anthropic, OpenAI) |
| src/auth/ | SAP authentication flows |
| src/bridge/ | Browser bridge + injection engine |
| └─ bridge/browser-scripts/ | 10 scripts evaluated in browser context |
| └─ bridge/interaction-strategies/ | 3 strategies (UI5Native, DomFirst, OPA5) |
| src/cli/ | CLI tooling |
| src/core/ | Foundation: errors, config, logging, types |
| └─ core/constants/ | Control types (199), timeouts, defaults |
| └─ core/errors/ | 14 error classes, 67 error codes |
| └─ core/logging/ | Pino structured logger |
| └─ core/types/ | 551 type definitions |
| └─ core/utils/ | Wait helpers, path helpers, compat |
| src/fe/ | SAP Fiori Elements support |
| src/fixtures/ | 11 Playwright fixture modules |
| src/intents/ | Intent-based test generation |
| src/matchers/ | 10 custom UI5 matchers |
| src/modules/ | Module system |
| src/proxy/ | UI5ControlProxy (8-step get trap) |
| src/reporters/ | Custom Playwright reporters |
| src/selectors/ | UI5 selector engine |
| src/vocabulary/ | SAP domain vocabulary |
| src/index.ts | Main entry: exports test, expect |
| src/version.ts | Package version |
Package Exports
6 sub-path exports with dual ESM + CJS output. Every export is validated by @arethetypeswrong/cli (attw) to ensure correct resolution in all bundlers.
| Import Path | ESM | CJS | Contents |
|---|---|---|---|
| playwright-praman | ./dist/index.js | ./dist/index.cjs | test, expect, fixtures, proxy, matchers |
| playwright-praman/ai | ./dist/ai/index.js | ./dist/ai/index.cjs | AI integration, healing, LLM adapters |
| playwright-praman/intents | ./dist/intents/index.js | ./dist/intents/index.cjs | Intent-based test generation |
| playwright-praman/vocabulary | ./dist/vocabulary/index.js | ./dist/vocabulary/index.cjs | SAP domain vocabulary system |
| playwright-praman/fe | ./dist/fe/index.js | ./dist/fe/index.cjs | SAP Fiori Elements helpers |
| playwright-praman/reporters | ./dist/reporters/index.js | ./dist/reporters/index.cjs | Custom Playwright reporters |
Built by tsup with format: ['esm','cjs' ], cjsInterop: true, shims: true. Each export includes matching .d.ts and .d.cts type declaration files.
Proxy Architecture
The UI5ControlProxy is the central abstraction that makes SAP UI5 controls feel like local TypeScript objects. It uses an ES Proxy get trap that resolves property access through an 8-step pipeline.
13 Built-in Method Overrides
These methods are intercepted by resolveKnownProperty() and handled specially — they do not go through the generic dynamic method forwarder.
Nine-Type Return Detection System
When a method is executed in the browser via page.evaluate(), the bridge classifies the return value into one of 9 types defined in BridgeReturnType. Each type triggers a different handler in the proxy.
| # | Type | Condition | Proxy Action |
|---|---|---|---|
| 1 | result | Primitive value (string, number, boolean) | Returns raw value directly |
| 2 | empty | void / undefined / null return | Returns undefined, enables chaining |
| 3 | element | Existing UI5 control (same control ref) | Wraps in new sub-proxy with control metadata |
| 4 | newElement | Newly discovered control reference | Creates fresh proxy with full discovery |
| 5 | aggregation | Array of child controls | Returns array of sub-proxies, each fully typed |
| 6 | object | Non-control UI5 object (model, binding) | Stored in objectMap with UUID, returns accessor proxy |
| 7 | objectArray | Array of non-control UI5 objects | Each stored with UUID in objectMap |
| 8 | none | Unclassified return (logged as warning) | Returns undefined with diagnostic logging |
| 9 | unknown | Instance check failed | Returns undefined with diagnostic logging |
Three-Tier Control Discovery
Control discovery is the mechanism by which Praman locates a UI5 control in the browser. The 3-tier approach ensures compatibility across UI5 versions (1.71 through 2.x) and handles complex selectors.
Execution Strategy Options
Praman provides 3 interaction strategies for invoking UI5 control methods. The strategy is selected per-test or per-action, giving fine-grained control over how interactions are executed.
| Strategy | Approach | Fallback Chain | Best For |
|---|---|---|---|
| UI5Native | Calls UI5 control API methods directly in browser context | firePress() → fireSelect() → fireTap() → DOM click | Standard SAP Fiori apps, most reliable |
| DomFirst | Performs DOM interaction first, falls back to UI5 API | DOM click() → firePress() → fireTap() | Hybrid apps, custom controls, non-standard handlers |
| OPA5 | Uses SAP OPA5 test framework's RecordReplay engine | RecordReplay.interactWithControl() → DOM fallback | Complex controls, SmartFields, value helps |
Technology Stack
Runtime Dependencies
| @playwright/test | >=1.57.0 <2.0.0 (peer) |
| commander | 14.0.3 — CLI framework |
| pino | 10.3.1 — structured logging |
| zod | 4.3.6 — schema validation |
Optional AI Dependencies
| @anthropic-ai/sdk | >=0.78.0 (optional peer) |
| openai | >=6.22.0 (optional peer) |
| @opentelemetry/api | >=1.9.0 (optional peer) |
| @opentelemetry/sdk-node | >=0.212.0 (optional peer) |
Build & Quality Tooling
| TypeScript | 5.9.3 — strict mode |
| tsup | 8.5.1 — dual ESM+CJS build |
| Vitest | 4.0.18 — unit testing |
| @vitest/coverage-v8 | 4.0.18 — per-file coverage |
| ESLint | 11 plugins, zero tolerance |
| attw | 0.18.2 — export validation |
| API Extractor | 7.56.3 — API surface |
| TypeDoc | 0.28.17 — docs generation |
| knip | 5.83.1 — dead code detection |
| cspell | 9.6.4 — spell checking |
Security & Compliance
Multi-layered security posture verified against actual CI workflows, ESLint config, package.json scripts, and GitHub repository settings. Every item below is implemented and enforced in code.
SBOM & Supply Chain
| Measure | Tool / Implementation | Details |
|---|---|---|
| SBOM Generation | @cyclonedx/cyclonedx-npm 4.1.2 | CycloneDX 1.5 JSON output; npm run generate:sbom; auto-generated in release pipeline |
| npm Provenance | npm publish --provenance | OIDC-signed provenance attestation on every npm publish; id-token: write in release.yml |
| SHA-Pinned Actions | Full SHA commit hashes | All actions in ci.yml, release.yml, docs.yml pinned to exact SHAs (not floating tags) |
| Dependency Lockfile | package-lock.json v3 | lockfileVersion 3 with SHA-512 integrity hashes; npm ci enforced in all CI steps |
| Dependabot | .github/dependabot.yml | Weekly updates for npm (prod: patch-only, dev: minor+patch) and github-actions ecosystems |
| License Inventory | license-report.json + ScanCode | Full dependency license audit; ScanCode Toolkit 32.0.2 scan of src/ for copyright/license detection |
| NOTICE File | Root NOTICE + LICENSE | Apache 2.0 license; NOTICE with attribution; both published to npm via files field |
Static Analysis & Code Security
| Measure | Tool / Implementation | Details |
|---|---|---|
| Microsoft SDL | @microsoft/eslint-plugin-sdl 1.1.0 | 8 SDL rules: no-insecure-url, no-inner-html, no-postmessage-star-origin, etc. |
| OWASP Security | eslint-plugin-security 3.0.1 | 12 rules: detect-eval, detect-unsafe-regex, detect-child-process, detect-non-literal-regexp |
| CodeQL Analysis | github/codeql-action (CI) | Weekly + PR scans with security-extended query suite for JavaScript/TypeScript |
| npm Audit | npm audit --audit-level=high | Runs in CI security job on every push/PR; --omit=dev excludes devDependencies |
| Code Quality | eslint-plugin-sonarjs 3.0.7 | Detects cognitive complexity, code duplication, code smells |
| Dead Code | knip 5.83.1 | Eliminates unused exports, dependencies, files from production surface |
| TypeScript Strict | strict + noUncheckedIndexedAccess | No any, exactOptionalPropertyTypes, verbatimModuleSyntax, isolatedModules |
Credential & Runtime Security
| Measure | Tool / Implementation | Details |
|---|---|---|
| Log Redaction | Pino redaction (14 paths) | Redacts password, token, apiKey, secret, authorization, cookie, sessionId, credentials, accessToken, refreshToken, bearerToken + auth/config paths |
| Env-Only Credentials | process.env + GitHub Secrets | SAP_CLOUD_*, SAP_ONPREM_*, ANTHROPIC_API_KEY — never hardcoded; passed via CI secrets |
| Bridge Isolation | WeakSet idempotency | ensureBridgeInjected() prevents double injection and memory leaks |
| Input Validation | Zod 4.3.6 schema validation | All external input validated at system boundaries before processing |
| .gitignore Secrets | .env, .env.local, .auth/ | Secret files excluded from version control |
| SECURITY.md | Responsible disclosure policy | Responsible disclosure via [email protected] |
CI/CD Security Gates
| Gate | Trigger | Enforcement |
|---|---|---|
| Pre-commit Hook | git commit | Husky: check-no-js-in-src + lint-staged (ESLint --fix, Prettier, markdownlint) |
| Commit Message | git commit | commitlint @commitlint/config-conventional via commit-msg hook |
| Pre-push Hook | git push | Husky: typecheck + unit tests with coverage + build — all must pass |
| CI Security Job | Push to main / PR | npm audit --audit-level=high --omit=dev — fails on HIGH/CRITICAL vulns |
| CodeQL Scan | Push to main / PR / Weekly | security-extended queries; results in GitHub Security tab |
| CODEOWNERS Review | Every PR | * @mrkanitkar — all files require owner review |
| Workflow Permissions | All CI workflows | Least-privilege: contents:read default, scoped write only where needed |
| Concurrency Control | ci.yml | cancel-in-progress: true — prevents resource exhaustion from stacked runs |
| License Headers | Every lint pass | eslint-plugin-headers enforces Apache 2.0 SPDX header as error on all src/ files |
Best Practice Alignment
| Standard | Practices Adopted |
|---|---|
| Playwright | Web-first assertions, fixture DI, project dependencies for auth, test.step() |
| Microsoft | TSDoc standard, API Extractor, SDL security, OpenTelemetry, SHA-pinned Actions, cross-platform CI |
| Google TS Style | Readonly<> config, no barrel re-exports of internals, strict type checking |
| Google SRE | Exponential backoff + jitter in retries, structured error codes |
| Google Testing | Tiered coverage (100% errors, 95% core, 90% global), per-file enforcement |
| Node.js | ESM-first with CJS fallback, node: prefix, engines field, files field, dual exports |
| Anthropic / Claude | retryable + suggestions[] on errors, AI response envelope, checkpoint serialization |
| npm | Dual ESM+CJS via conditional exports, validated by attw, clean package.json files field |
Documentation Strategy
| Layer | Tool | Output |
|---|---|---|
| Inline Documentation | Microsoft TSDoc with custom tags (@intent, @guarantee, @capability, @recipe, @ai, @aiContext, @sapModule, @businessContext) | Every public function has TSDoc + @example |
| API Reference | @microsoft/api-extractor + TypeDoc 0.28.17 | Generated API surface documentation |
| Linting | eslint-plugin-tsdoc (syntax: 'error') | Zero tolerance on TSDoc syntax violations |
| Website | Docusaurus 3.x with custom React pages | Architecture, features, personas, demo |
| Agent Instructions | CLAUDE.md, AGENTS.md, .cursor/rules/, .github/copilot-instructions.md | 7 IDE/agent configurations |
| Skill Files | 12 skill files in skills/playwright-praman-sap-testing/ | Agent-readable domain expertise |
ESLint Plugin Coverage (11 Plugins)
Zero errors, zero warnings policy. All 11 plugins run on every lint pass.
Tiered Coverage Strategy
Google/Microsoft best practice: per-file enforcement ensures no single file hides behind project averages.
| Tier | Scope | Statements | Branches | Functions | Lines |
|---|---|---|---|---|---|
| Tier 1 | Error classes, public API (src/core/errors/) | 100% | 100% | 100% | 100% |
| Tier 2 | Core infrastructure (src/core/) | 95% | 90% | 95% | 95% |
| Tier 3 | All other modules (global) | 90% | 85% | 90% | 90% |
Tool: @vitest/coverage-v8 with perFile: true. Reporters: text, lcov, json-summary, json, html. Watermarks: Yellow 80-95%, Green 95%+.
Cloud Execution
Azure Playwright Workspaces Integration
Run SAP E2E tests at scale with cloud-hosted browsers. GitHub Actions runs your test code, Azure runs the browsers, SAP Cloud is tested end-to-end — no local machine required.
- Test scripts
- Configs
- Secrets
- Node.js workers
- playwright-praman
- Fixtures & assertions
- DOM rendering
- page.evaluate()
- UI5 bridge scripts
- Fiori Launchpad
- OData services
- Business data
Acknowledgments
Praman was developed independently and does not contain code from the projects listed below. We would like to thank the following open-source project whose architectural patterns served as inspiration during Praman’s design:
The wdi5 project by the UI5 Community pioneered the approach of bridging Playwright (and WebdriverIO) with SAP UI5’s control model in the browser. Architectural patterns for SAP UI5 browser bridge design were studied during Praman’s independent, ground-up implementation. We are grateful to the wdi5 maintainers and contributors for their excellent open-source work under the Apache License 2.0.