Architecture Overview
Praman v1.0 ships as a single npm package (playwright-praman) with sub-path exports, organized into a strict 5-layer architecture where lower layers never import from higher layers.
5-Layer Architecture
┌─────────────────────────────────────────────────────────────────────────┐
│ Test Author / AI Agent │
│ import { test, expect } from 'playwright-praman' │
│ import { procurement } from 'playwright-praman/intents' │
└──────────────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────────────▼──────────────────────────────────────┐
│ Layer 5 — Public API (6 sub-path exports) │
│ │
│ playwright-praman /ai /intents /vocabulary /fe /reporters │
└──────────────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────────────▼──────────────────────────────────────┐
│ Layer 4 — Fixtures + Modules │
│ │
│ Fixtures core · auth · nav · stability · ai · fe · intent · flp │
│ Merged via mergeTests() into a single test object │
│ Modules table · dialog · date · navigation · odata · workzone │
│ Auth cloud-saml · office365 · onprem · api-key · cert · tenant │
└──────────────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────────────▼──────────────────────────────────────┐
│ Layer 3 — Control Proxy │
│ │
│ control-proxy.ts · discovery.ts · ui5-object.ts · cache.ts │
│ 3-tier discovery: object cache → stable ID → type + property search │
└──────────────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────────────▼──────────────────────────────────────┐
│ Layer 2 — Bridge │
│ │
│ injection.ts (lazy bridge bootstrap into browser context) │
│ browser-scripts/ inject-ui5 · find-control · execute-method · ... │
│ Interaction strategies: UI5Native · DomFirst · OPA5 │
└──────────────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────────────▼──────────────────────────────────────┐
│ Layer 1 — Core Infrastructure │
│ │
│ config (Zod) · errors (13 subclasses) · logging (pino) │
│ telemetry (OTel) · types (199 interfaces) · utils · compat │
└──────────────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────────────▼──────────────────────────────────────┐
│ @playwright/test (external dependency) │
│ page · browser · context · expect │
└─────────────────────────────────────────────────────────────────────────┘
Layer Dependency Rules
The architecture enforces a strict downward-only dependency rule:
- Layer 1 (Core) has zero internal dependencies beyond Node.js builtins.
- Layer 2 (Bridge) imports only from Layer 1.
- Layer 3 (Proxy) imports from Layers 1 and 2.
- Layer 4 (Fixtures + Modules) imports from Layers 1, 2, and 3.
- Layer 5 (Public API) re-exports from Layers 1–4; no higher layer imports from it.
This is enforced at compile time via TypeScript path aliases (#core/*, #bridge/*, #proxy/*, #fixtures/*) and ESLint import rules.
The aliases below are internal to Praman's source code and are not available to package consumers. They enforce layer boundaries during development.
// Allowed: Layer 3 importing from Layer 1
import { ErrorCode } from '#core/errors/codes.js';
// Allowed: Layer 3 importing from Layer 2
import { ensureBridgeInjected } from '#bridge/injection.js';
// FORBIDDEN: Layer 1 importing from Layer 3 — compile error
// import { ControlProxy } from '#proxy/control-proxy.js';
Sub-Path Exports
Praman ships 6 sub-path exports, each targeting a specific concern:
| Sub-Path Export | Purpose | Key Modules |
|---|---|---|
playwright-praman | Core fixtures, expect, test | Fixtures, selectors, matchers |
playwright-praman/ai | AI agent integration | CapabilityRegistry, RecipeRegistry, AgenticHandler |
playwright-praman/intents | SAP domain intent APIs | Core wrappers, 5 domain namespaces |
playwright-praman/vocabulary | Business term resolution | VocabularyService, fuzzy matcher |
playwright-praman/fe | Fiori Elements helpers | ListReport, ObjectPage, FE tables |
playwright-praman/reporters | Custom Playwright reporters | ComplianceReporter, ODataTraceReporter |
Each export provides both ESM and CJS outputs:
{
"./ai": {
"types": {
"import": "./dist/ai/index.d.ts",
"require": "./dist/ai/index.d.cts"
},
"import": "./dist/ai/index.js",
"require": "./dist/ai/index.cjs"
}
}
All exports are validated by @arethetypeswrong/cli (attw) in CI.
Design Decisions Summary (D1–D29)
The architecture is guided by 29 documented design decisions:
| # | Decision | Category |
|---|---|---|
| D1 | Single package with sub-path exports (not monorepo) | Packaging |
| D2 | Internal fixture composition via extend() chain | Fixtures |
| D3 | Bridge | |
| D4 | Hybrid typed proxy: 199 typed interfaces + dynamic fallback | Proxy |
| D5 | 4-layer observability: Reporter + pino + OTel + AI telemetry | Observability |
| D6 | Zod validation at external boundaries only | Validation |
| D7 | Zod-validated praman.config.ts with env overrides | Config |
| D8 | Unified PramanError hierarchy with 13 subclasses | Errors |
| D9 | AI Mode A (SKILL.md) + Mode C (agentic fixture) | AI |
| D10 | Vitest unit tests + Playwright integration tests | Testing |
| D11 | No plugin API in v1.0 | Extension |
| D12 | Auto-generated SKILL.md + Docusaurus + TypeDoc | Docs |
| D13 | Apache 2.0 license | Legal |
| D14 | Playwright >=1.57.0 <2.0.0 with CI matrix | Compat |
| D15 | Security: dep scanning, secret redaction, SBOM, provenance | Security |
| D16 | Single unified proxy (no double-proxy) | Proxy |
| D17 | Bidirectional proxy conversion (UI5Object <-> UI5ControlProxy) | Proxy |
| D18 | Integrated discovery factory (removed dead code) | Discovery |
| D19 | Centralized __praman_getById() (3-tier API resolution) | Bridge |
| D20 | Browser objectMap with TTL + cleanup | Memory |
| D21 | Shared interaction logic across strategies | Interactions |
| D22 | Auto-generated method signatures (199 interfaces, 4,092 methods) | Types |
| D23 | skipStabilityWait as global config + per-selector override | Stability |
| D24 | exec() with new Function() + security documentation | Security |
| D25 | Visibility preference default (prefer visible controls) | Discovery |
| D26 | UI5Object AI introspection as first-class capability | AI |
| D27 | Module size <= 300 LOC guideline (with documented exceptions) | Quality |
| D28 | Auth via Playwright project dependencies (not globalSetup) | Auth |
| D29 | Enhanced error model + AI response envelope | AI/Errors |
Module Decomposition Rationale
The predecessor plugin (v2.5.0) suffered from monolithic files:
| File | LOC | Problem |
|---|---|---|
ui5-handler.ts | 2,318 | God object handling all UI5 operations |
plugin-fixtures.ts | 2,263 | All 32 fixtures in one file |
ui5-control-proxy.ts | 1,829 | Double-proxy with redundant interception |
Praman v1.0 decomposes these into focused modules following SRP (Single Responsibility Principle):
Fixtures are split by domain:
core-fixtures.ts— UI5 control operationsauth-fixtures.ts— Authentication strategiesnav-fixtures.ts— Navigation and routingstability-fixtures.ts— Wait conditions and stability checksai-fixtures.ts— AI agent fixturesfe-fixtures.ts— Fiori Elements fixturesintent-fixtures.ts— Intent API fixturesshell-footer-fixtures.ts,flp-locks-fixtures.ts,flp-settings-fixtures.ts— FLP-specific fixtures
Modules handle domain operations independently of fixtures:
table.ts+table-operations.ts+table-filter-sort.ts— Grid and SmartTabledialog.ts— Dialog detection and interactiondate.ts— DatePicker and range fieldsnavigation.ts— UI5 routing and hash navigationodata.ts+odata-http.ts— OData model access and CRUDworkzone.ts— BTP WorkZone helpers
UI5Handler was decomposed from a 2,318 LOC god object into:
ui5-handler.ts(588 LOC) — 16 core methodsshell-handler.ts(102 LOC) — FLP shell operationsfooter-handler.ts(119 LOC) — Footer bar operationsnavigation.ts(201 LOC) — Route and hash navigation
Control proxy was simplified from a double-proxy pattern (1,829 LOC) into:
control-proxy.ts(653 LOC) — Unified single proxy handlerui5-object.ts(383 LOC) — Non-control object proxydiscovery.ts(111 LOC) — 3-tier control discoverycache.ts(104 LOC) — Proxy cache with RegExp keys
Every module targets <= 300 LOC. Exceptions are documented with justification comments.
Codebase Metrics
| Metric | Value |
|---|---|
| Source files | ~150 |
| Source LOC | ~29,000 |
| Public functions | 208 |
| Test files | 109 |
| Unit tests | ~2,000 passing |
| Typed control interfaces | 199 (4,092 methods) |
| Error subclasses | 13 |
| Coverage | >98% (per-file enforcement) |
| Lint errors | 0 |
| Type errors | 0 |
| ESM + CJS dual build | Validated by attw |