Skip to main content

The Enterprise POC Playbook: 9 Scenarios to Validate Playwright-Praman for SAP Test Automation

· 24 min read
Maheshwar Kanitkar
Creator of Praman

Your SAP landscape has 200+ Fiori apps, quarterly transport cycles that break tests, and a test automation backlog measured in person-years. You have heard the pitch: AI-first test automation for SAP UI5. But before committing budget, headcount, and political capital, you need proof.

This guide is the POC playbook. It covers the 9 hardest scenarios that separate SAP test automation tools from SAP test automation solutions — and gives you exact steps, agent commands, code, and scoring criteria to evaluate Playwright-Praman in your own landscape.

Who This Guide Is For

  • Test Automation Leads evaluating tools for an SAP Center of Excellence
  • QA Architects building the business case for Playwright adoption
  • SAP Basis / DevOps Engineers who need to validate CI/CD integration
  • Decision Makers who need a structured go/no-go framework with measurable outcomes

The Agent-First Methodology

The single most important thing to understand about a Praman POC is this: you do not start by writing test code. You start by pointing an AI agent at your live SAP app and letting it discover what is on the page.

Praman ships three agents — Planner, Generator, and Healer — that form a closed loop:

┌─────────┐ test plan ┌───────────┐ spec file ┌────────┐
│ Planner │ ──────────────────▶ │ Generator │ ──────────────────▶ │ Healer │
│ (explore)│ │ (generate) │ │ (fix) │
└─────────┘ └───────────┘ └────────┘
▲ │
│ feedback loop │
└───────────────────────────────────────────────────────────────┘

Every POC scenario below follows the same 3-step agent workflow:

  1. Plan — the Planner agent opens a headed browser, explores the SAP app, discovers UI5 controls, captures ARIA snapshots, and produces a structured test plan.
  2. Generate — the Generator agent reads the plan and produces a compliant Playwright-Praman spec file using fixtures, test.step() pattern, and proper SAP control interactions.
  3. Heal — when tests fail (due to transport drift, environment differences, or selector changes), the Healer agent debugs the failure, inspects the live page, and fixes the spec.

This is not a theoretical workflow. It is the primary methodology for the POC.

Agent Invocation — Two Modes

CLI Agents (token-efficient, works in any terminal):

# Plan: explore the app and produce a test plan
npx playwright-praman plan --url "$SAP_URL" --auth .auth/sap-session.json

# Generate: produce a spec from the plan
npx playwright-praman generate --plan tests/plans/po-list-report.plan.json

# Heal: fix a failing test
npx playwright-praman heal --spec tests/e2e/po-list-report.spec.ts

MCP Agents (richer IDE integration — VS Code Copilot, Cursor, JetBrains AI):

# Claude Code prompts
/praman-sap-plan # Run planner on SAP app
/praman-sap-generate # Generate tests from plan
/praman-sap-heal # Fix failing tests
/praman-sap-coverage # Full pipeline: plan → generate → heal

The POC scorecard evaluates agents first, manual code second. If agents can handle a scenario end-to-end, that is the strongest possible validation. If agents produce 80% and a human finishes the remaining 20%, that is still a massive productivity win.


POC Timeline: 4-Week Sprint

WeekPhaseScenariosDeliverable
1FoundationEnvironment setup, Fiori Elements (#1), Smart Tables/Forms (#2)Agent-generated specs for 2 apps, baseline metrics
2Enterprise WorkflowsProcurement workflow (#3), Role-based auth (#4), Transport drift (#5)Multi-step workflow spec, auth matrix, cross-environment results
3Resilience & ScaleCI parallelization (#6), Flaky network (#7), AI healing (#8)CI pipeline, resilience report, healing accuracy metrics
4Differentiators & DecisionOData assertions (#9), scoring, final reportCompleted scorecard, go/no-go recommendation

Team

RoleCommitmentResponsibilities
Test Automation Engineer (lead)Full-time, 4 weeksDrive all 9 scenarios, run agents, measure results
SAP Basis / Security2-3 days across Weeks 1-2Auth configuration, test users, role assignment, system access
DevOps / CI Engineer3-4 days in Week 3Pipeline setup, sharding, artifact management
QA Manager / Decision Maker2 days in Week 4Review scorecard, make go/no-go decision

Prerequisites

Before Week 1 begins:

# 1. Install
npm init -y
npm install playwright-praman @playwright/test
npx playwright install chromium

# 2. Initialize Praman (scaffolds config, agents, seed test)
npx playwright-praman init

# 3. Configure SAP credentials (.env file)
SAP_BASE_URL=https://your-sap-system.example.com
SAP_USERNAME=TEST_AUTOMATION_USER
SAP_PASSWORD=<from-vault>
SAP_CLIENT=100
SAP_LANGUAGE=EN

# 4. Verify connection with seed test
npx playwright test --project=agent-seed-test

You need: a SAP system with test users, network access from CI runners to SAP, and a Chromium-capable environment.


Scenario 1: Fiori Elements App

Why this is first: Fiori Elements apps are the hardest UI5 test target. The DOM is generated at runtime from CDS annotations. Control IDs are unstable. Responsive layouts shift controls between desktop and tablet breakpoints. If Praman can handle a Fiori Elements List Report + Object Page, it can handle anything in your SAP landscape.

Agent-First Approach

# Step 1: Point the planner at a real Fiori Elements List Report
npx playwright-praman plan \
--url "$SAP_BASE_URL/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html#PurchaseOrder-manage" \
--auth .auth/sap-session.json \
--output tests/plans/po-list-report.plan.json

The planner will:

  • Navigate to the app via Fiori Launchpad
  • Wait for UI5 to stabilize (waitForUI5)
  • Discover all controls: SmartFilterBar, SmartTable, toolbar buttons, variant management
  • Capture the ARIA accessibility snapshot for AI grounding
  • Produce a structured plan with control types, selectors, and interaction sequence
# Step 2: Generate the spec
npx playwright-praman generate \
--plan tests/plans/po-list-report.plan.json \
--output tests/e2e/po-list-report.spec.ts

What the Generated Spec Looks Like

import { test, expect } from 'playwright-praman';

test.describe('Purchase Order - List Report', () => {
test('filter, search, and navigate to Object Page', async ({ ui5, page }) => {
await test.step('Open List Report via FLP', async () => {
await page.goto(
`${process.env.SAP_BASE_URL}/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html#PurchaseOrder-manage`,
);
await ui5.waitForUI5();
});

await test.step('Set filters on SmartFilterBar', async () => {
const filterBar = await ui5.control({
controlType: 'sap.ui.comp.smartfilterbar.SmartFilterBar',
});

await ui5.fill(
{ controlType: 'sap.m.Input', ancestor: filterBar, properties: { name: 'CompanyCode' } },
'1000',
);
await ui5.fill(
{ controlType: 'sap.m.Input', ancestor: filterBar, properties: { name: 'Supplier' } },
'100001',
);
});

await test.step('Execute search', async () => {
await ui5.press({
controlType: 'sap.m.Button',
properties: { text: 'Go' },
});
await ui5.waitForUI5();
});

await test.step('Verify table has results', async () => {
const table = await ui5.control({
controlType: 'sap.ui.comp.smarttable.SmartTable',
});
const rowCount = await ui5.table.getRowCount(table);
expect(rowCount).toBeGreaterThan(0);
});

await test.step('Navigate to first item (Object Page)', async () => {
await ui5.table.selectRow(0);
await ui5.waitForUI5();

// Verify Object Page loaded
const objectHeader = await ui5.control({
controlType: 'sap.uxap.ObjectPageHeader',
});
expect(objectHeader).toBeTruthy();
});
});
});

Scoring Criteria

MetricTargetHow to Measure
Agent discovery completeness>90% of visible controls foundCompare plan output vs. manual inspection
Generated spec pass rateFirst-run pass on 3/5 attemptsRun spec 5 times, count passes
Selector stability<5% flaky rate over 20 runsRun spec 20 times, count failures
Object Page navigationWorks without manual fixAgent-generated navigation succeeds
Execution time<60s per testMeasure wall-clock time

Scenario 2: Smart Tables and Forms

Why this matters: SmartTable, SmartFilterBar, and SmartForm are the backbone of SAP Fiori apps. They render dynamically from OData metadata, generate columns/fields at runtime, and use Value Help dialogs for validated input. Every SAP team struggles with automating these controls.

Agent-First Approach

# Plan against an app with SmartForm (e.g., Object Page in edit mode)
npx playwright-praman plan \
--url "$SAP_BASE_URL/...#Material-manage&/Material('MAT001')" \
--auth .auth/sap-session.json \
--interactions edit,fill,valuehelp \
--output tests/plans/material-smart-form.plan.json

The planner specifically discovers:

  • SmartField inner control types (Input, ComboBox, DatePicker — determined by OData metadata)
  • Value Help button associations
  • Required/optional field states
  • Field groups and their layout

Key Patterns the Agent Must Handle

await test.step('Fill SmartField with Value Help', async () => {
// SmartFields wrap inner controls — agent must detect the actual input type
await ui5.fill(
{
controlType: 'sap.ui.comp.smartfield.SmartField',
bindingPath: { path: 'Supplier' },
},
'100001',
);

// If the SmartField has a Value Help, the agent opens and selects from it
await ui5.press({
controlType: 'sap.ui.core.Icon',
properties: { src: 'sap-icon://value-help' },
ancestor: {
controlType: 'sap.ui.comp.smartfield.SmartField',
bindingPath: { path: 'Supplier' },
},
});

// CRITICAL: searchOpenDialogs for any control inside a dialog
const dialogInput = await ui5.control({
controlType: 'sap.m.SearchField',
searchOpenDialogs: true,
});
await ui5.fill(dialogInput, '100001');
await ui5.press({
controlType: 'sap.m.Button',
properties: { text: 'Go' },
searchOpenDialogs: true,
});

// Select from results table in dialog
await ui5.table.selectRow(0, { searchOpenDialogs: true });
await ui5.press({
controlType: 'sap.m.Button',
properties: { text: 'OK' },
searchOpenDialogs: true,
});
});

Scoring Criteria

MetricTargetHow to Measure
SmartField type detectionAgent identifies inner control type for >90% of fieldsReview plan output
Value Help automationAgent handles open → search → select → confirmRun against 3 different Value Help dialogs
searchOpenDialogs usageAgent always uses it for dialog controlsCode review of generated spec
Required field validationAgent detects and fills all mandatory fieldsLeave a required field empty, verify error handling

Scenario 3: Long Procurement Workflow

Why this matters: Real enterprise tests are not single-screen interactions. A procurement workflow crosses 4-5 apps, multiple user roles, and takes 15+ minutes to execute manually: Purchase Requisition → Purchase Order → Goods Receipt → Invoice Receipt → Payment. If the tool cannot chain multi-app workflows with data handoff between steps, it is a non-starter.

Agent-First Approach — Chained Plans

# Plan each step separately, then chain
npx playwright-praman plan --url "$SAP_URL#PurchaseRequisition-create" \
--auth .auth/sap-session.json \
--output tests/plans/01-create-pr.plan.json

npx playwright-praman plan --url "$SAP_URL#PurchaseOrder-create" \
--auth .auth/sap-session.json \
--output tests/plans/02-create-po.plan.json

npx playwright-praman plan --url "$SAP_URL#InboundDelivery-create" \
--auth .auth/sap-session.json \
--output tests/plans/03-goods-receipt.plan.json

# Generate a combined spec
npx playwright-praman generate \
--plan tests/plans/01-create-pr.plan.json \
--plan tests/plans/02-create-po.plan.json \
--plan tests/plans/03-goods-receipt.plan.json \
--output tests/e2e/procurement-workflow.spec.ts

Generated Workflow Spec Pattern

import { test, expect } from 'playwright-praman';

test.describe('Procurement End-to-End', () => {
let prNumber: string;
let poNumber: string;
let grDocument: string;

test('complete procurement cycle', async ({ ui5, page }) => {
await test.step('Step 1: Create Purchase Requisition', async () => {
await page.goto(`${process.env.SAP_BASE_URL}#PurchaseRequisition-create`);
await ui5.waitForUI5();

await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', bindingPath: { path: 'Material' } },
'MAT-POC-001',
);
await ui5.fill(
{
controlType: 'sap.ui.comp.smartfield.SmartField',
bindingPath: { path: 'OrderQuantity' },
},
'100',
);
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', bindingPath: { path: 'Plant' } },
'1000',
);

await ui5.press({ controlType: 'sap.m.Button', properties: { text: 'Save' } });
await ui5.waitForUI5();

// Capture PR number for next step
const message = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
const text = await message.getProperty('text');
prNumber = text.match(/(\d{10})/)?.[1] ?? '';
expect(prNumber).toMatch(/^\d{10}$/);
});

await test.step('Step 2: Create Purchase Order from PR', async () => {
await page.goto(`${process.env.SAP_BASE_URL}#PurchaseOrder-create`);
await ui5.waitForUI5();

await ui5.fill(
{
controlType: 'sap.ui.comp.smartfield.SmartField',
bindingPath: { path: 'PurchaseRequisition' },
},
prNumber,
);
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', bindingPath: { path: 'Supplier' } },
'100001',
);

await ui5.press({ controlType: 'sap.m.Button', properties: { text: 'Save' } });
await ui5.waitForUI5();

const message = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
const text = await message.getProperty('text');
poNumber = text.match(/(\d{10})/)?.[1] ?? '';
expect(poNumber).toMatch(/^\d{10}$/);
});

await test.step('Step 3: Post Goods Receipt', async () => {
await page.goto(`${process.env.SAP_BASE_URL}#InboundDelivery-create`);
await ui5.waitForUI5();

await ui5.fill(
{
controlType: 'sap.ui.comp.smartfield.SmartField',
bindingPath: { path: 'PurchaseOrder' },
},
poNumber,
);

await ui5.press({ controlType: 'sap.m.Button', properties: { text: 'Post' } });
await ui5.waitForUI5();

const message = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
const text = await message.getProperty('text');
grDocument = text.match(/(\d{10})/)?.[1] ?? '';
expect(grDocument).toMatch(/^\d{10}$/);
});

await test.step('Verify: all documents created', async () => {
expect(prNumber).toBeTruthy();
expect(poNumber).toBeTruthy();
expect(grDocument).toBeTruthy();
});
});
});

Scoring Criteria

MetricTargetHow to Measure
Cross-app navigationAgent produces plans for all 3+ appsReview generated plans
Data handoff (PR → PO → GR)Document numbers passed correctly between stepsEnd-to-end run succeeds
Total execution time<5 minutes for 3-step workflowWall-clock measurement
Error recoveryClear failure message identifying which step failedDeliberately break step 2, check output
test.step() structureEach business step is a named stepCode review

Scenario 4: Role-Based Authorization Changes

Why this is SAP-specific: The same Fiori app shows different controls to different users based on PFCG authorization roles. A procurement manager sees "Approve" and "Reject" buttons; a requisitioner sees only "Submit." A test that passes for one role may fail for another — not because of a bug, but because of intentional authorization design.

Agent-First Approach — Multi-Role Plans

# Plan as Requisitioner
SAP_USERNAME=TEST_REQUISITIONER npx playwright-praman plan \
--url "$SAP_URL#PurchaseRequisition-manage" \
--auth .auth/requisitioner.json \
--output tests/plans/pr-role-requisitioner.plan.json

# Plan as Approver
SAP_USERNAME=TEST_APPROVER npx playwright-praman plan \
--url "$SAP_URL#PurchaseRequisition-manage" \
--auth .auth/approver.json \
--output tests/plans/pr-role-approver.plan.json

# Diff the plans to see authorization-driven control differences
diff tests/plans/pr-role-requisitioner.plan.json tests/plans/pr-role-approver.plan.json

Playwright Config — Role-Based Projects

// playwright.config.ts
import { defineConfig } from 'playwright-praman';

export default defineConfig({
projects: [
// Auth setup per role
{
name: 'auth-requisitioner',
testMatch: /auth-setup\.ts/,
use: {
pramanConfig: {
auth: {
username: process.env.SAP_USER_REQUISITIONER!,
password: process.env.SAP_PASS_REQUISITIONER!,
storageStatePath: '.auth/requisitioner.json',
},
},
},
},
{
name: 'auth-approver',
testMatch: /auth-setup\.ts/,
use: {
pramanConfig: {
auth: {
username: process.env.SAP_USER_APPROVER!,
password: process.env.SAP_PASS_APPROVER!,
storageStatePath: '.auth/approver.json',
},
},
},
},
// Tests per role
{
name: 'requisitioner-tests',
dependencies: ['auth-requisitioner'],
testMatch: /requisitioner\.spec\.ts/,
use: { storageState: '.auth/requisitioner.json' },
},
{
name: 'approver-tests',
dependencies: ['auth-approver'],
testMatch: /approver\.spec\.ts/,
use: { storageState: '.auth/approver.json' },
},
],
});

Authorization-Aware Test Pattern

test('Requisitioner cannot see Approve button', async ({ ui5, page }) => {
await page.goto(`${process.env.SAP_BASE_URL}#PurchaseRequisition-manage`);
await ui5.waitForUI5();

// Verify Submit is visible
const submitBtn = await ui5.control({
controlType: 'sap.m.Button',
properties: { text: 'Submit' },
});
expect(submitBtn).toBeTruthy();

// Verify Approve is NOT visible (authorization-restricted)
const approveButtons = await ui5.controls({
controlType: 'sap.m.Button',
properties: { text: 'Approve' },
});
expect(approveButtons).toHaveLength(0);
});

Scoring Criteria

MetricTargetHow to Measure
Agent detects role-specific controlsPlans for 2 roles show different control setsDiff plan outputs
Negative assertionsAgent generates "button NOT visible" checksCode review
Auth isolationSessions don't leak between rolesRun role-specific tests in parallel
Role count supported3+ roles in single configAdd a 3rd role, verify config works

Scenario 5: Transport-Induced UI Drift

Why this is the #1 maintenance problem: In SAP, transports carry code and configuration changes from DEV → QA → PROD. A transport that adds a field to a CDS view, changes an annotation, or modifies a custom extension changes the UI without any code deployment. Tests that passed yesterday fail today — not because someone pushed a code change, but because a transport arrived.

This is the scenario where AI healing proves its value.

The Experiment

  1. Week 1: Run the Planner against your QA system. Generate specs. Verify they pass.
  2. Week 2: After a transport cycle imports changes, run the same specs without modification.
  3. Measure: How many fail? What broke? How fast can the Healer fix them?

Healer Agent in Action

# Tests fail after transport import
npx playwright test tests/e2e/po-list-report.spec.ts
# ✗ 3 failed, 2 passed

# Run the Healer
npx playwright-praman heal \
--spec tests/e2e/po-list-report.spec.ts \
--output tests/e2e/po-list-report.spec.ts # in-place fix

The Healer:

  • Reads the failing spec and error messages
  • Opens a headed browser to the failing page
  • Discovers the current control tree
  • Identifies what changed (renamed field, moved control, new required field)
  • Updates selectors, adds missing steps, adjusts assertions
  • Writes the fixed spec

Cross-Environment Validation

// playwright.config.ts — same tests, different SAP systems
export default defineConfig({
projects: [
{
name: 'dev',
use: {
baseURL: process.env.SAP_DEV_URL,
storageState: '.auth/dev-session.json',
},
},
{
name: 'qa',
use: {
baseURL: process.env.SAP_QA_URL,
storageState: '.auth/qa-session.json',
},
},
{
name: 'prod-smoke',
use: {
baseURL: process.env.SAP_PROD_URL,
storageState: '.auth/prod-session.json',
},
},
],
});
# Run same tests across all environments
npx playwright test --project=dev --project=qa --project=prod-smoke

Scoring Criteria

MetricTargetHow to Measure
Post-transport failure rate<30% of tests break after normal transportCount failures after a real transport cycle
Healer fix rate>80% of failures fixed automaticallyRun healer, count auto-fixed vs. manual-fix
Healing time<3 min per failing testMeasure healer wall-clock time
Cross-environment portabilitySame spec runs on DEV + QARun with --project=dev --project=qa
False positives0 tests "fixed" by removing valid assertionsCode review healer output

Scenario 6: CI Parallelization

Why this tests scalability: An enterprise SAP test suite has 500-2,000 tests. If each takes 30 seconds, a serial run takes 4-16 hours. Parallelization is not a nice-to-have — it is a gate to adoption. But SAP apps are stateful: parallel tests that modify the same data produce flaky results. The POC must validate that Praman handles this correctly.

Sharding Configuration

# .github/workflows/sap-e2e.yml
name: SAP E2E Tests
on: [push, pull_request]

jobs:
e2e:
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm ci
- run: npx playwright install chromium

# Run tests with sharding
- run: npx playwright test --shard=${{ matrix.shard }}/6
env:
SAP_BASE_URL: ${{ secrets.SAP_QA_URL }}
SAP_USERNAME: ${{ secrets.SAP_USERNAME }}
SAP_PASSWORD: ${{ secrets.SAP_PASSWORD }}

# Upload results for merge
- uses: actions/upload-artifact@v4
if: always()
with:
name: results-${{ matrix.shard }}
path: test-results/

merge-reports:
needs: e2e
if: always()
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: results-*
merge-multiple: true
- run: npx playwright merge-reports test-results --reporter=html

Data Isolation Per Worker

test('edit PO assigned to this shard', async ({ ui5, page }, testInfo) => {
// Each worker gets a unique PO from the pool
const poPool = [
'4500000001',
'4500000002',
'4500000003',
'4500000004',
'4500000005',
'4500000006',
];
const myPO = poPool[testInfo.workerIndex % poPool.length];

await page.goto(`${process.env.SAP_BASE_URL}#PurchaseOrder-manage&/PurchaseOrder('${myPO}')`);
await ui5.waitForUI5();

// Safe to edit — no other worker touches this PO
await ui5.press({ controlType: 'sap.m.Button', properties: { text: 'Edit' } });
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', bindingPath: { path: 'Note' } },
`Updated by shard ${testInfo.workerIndex}`,
);
await ui5.press({ controlType: 'sap.m.Button', properties: { text: 'Save' } });
});

Scaling Benchmark

Run the suite at 1, 2, 4, and 6 shards and record:

ShardsWall-Clock TimePass RateCost (CI minutes)
1baselinebaselinebaseline
2
4
6

Scoring Criteria

MetricTargetHow to Measure
Linear scaling4 shards = ~4x speedup (accounting for overhead)Benchmark table above
No data collision0 flaky tests caused by concurrent data accessRun 6-shard suite 3 times
Report mergingSingle HTML report from all shardsCheck merged report artifact
SAP session handlingNo auth failures under parallel loadMonitor SAP ICM logs

Scenario 7: Flaky Network Conditions

Why this tests resilience: SAP Gateway calls fail. OData batch requests time out. Corporate proxies drop connections. SSL certificates expire mid-session. If a test framework does not handle transient network failures gracefully, every flaky network event becomes a false-negative test failure — and your team stops trusting the results.

Simulating Network Degradation

import { test, expect } from 'playwright-praman';

test.describe('Network resilience', () => {
test('survives slow OData response', async ({ ui5, page, context }) => {
// Throttle all OData calls to 3G speed
const cdp = await context.newCDPSession(page);
await cdp.send('Network.emulateNetworkConditions', {
offline: false,
downloadThroughput: (500 * 1024) / 8, // 500 Kbps
uploadThroughput: (500 * 1024) / 8,
latency: 400, // 400ms RTT
});

await page.goto(`${process.env.SAP_BASE_URL}#PurchaseOrder-manage`);
await ui5.waitForUI5(); // Must succeed despite slow network

await ui5.press({ controlType: 'sap.m.Button', properties: { text: 'Go' } });
await ui5.waitForUI5();

const table = await ui5.control({
controlType: 'sap.ui.comp.smarttable.SmartTable',
});
expect(table).toBeTruthy();
});

test('handles OData 503 with retry', async ({ ui5, page }) => {
let requestCount = 0;

// Simulate gateway returning 503 on first attempt
await page.route('**/sap/opu/odata/**', async (route) => {
requestCount++;
if (requestCount <= 2) {
await route.fulfill({ status: 503, body: 'Service Unavailable' });
} else {
await route.continue();
}
});

await page.goto(`${process.env.SAP_BASE_URL}#PurchaseOrder-manage`);

// Praman's waitForUI5 includes retry logic for transient OData failures
await ui5.waitForUI5({ timeout: 60_000 });
});

test('produces actionable error on persistent failure', async ({ ui5, page }) => {
// Block all OData calls permanently
await page.route('**/sap/opu/odata/**', (route) =>
route.fulfill({ status: 500, body: 'Internal Server Error' }),
);

await page.goto(`${process.env.SAP_BASE_URL}#PurchaseOrder-manage`);

// Expect a clear, structured error — not a generic timeout
await expect(async () => {
await ui5.waitForUI5({ timeout: 15_000 });
}).rejects.toThrow(/ERR_/); // Praman error code, not generic Playwright timeout
});
});

Scoring Criteria

MetricTargetHow to Measure
Slow network toleranceTests pass at 500Kbps with 400ms latencyRun throttled suite
Transient 503 recoverywaitForUI5 retries and succeedsSimulate 503 then recover
Error message qualityStructured PramanError with code, suggestionsInspect error output on persistent failure
Timeout configurationConfigurable per-test and globalOverride timeout in single test

Scenario 8: AI Healing Quality

Why you must validate this: Every test automation vendor claims "AI-powered self-healing." Most deliver regex-based selector fallback that handles trivial ID changes and breaks on anything structural. The POC must measure healing accuracy against real SAP UI drift patterns — not toy examples.

The Controlled Experiment

Method: Take a passing test. Introduce 5 categories of breakage. Run the Healer on each. Measure success rate.

Breakage TypeHow to SimulateReal-World Cause
ID changeRename control ID in the app viewApp redeployment regenerates IDs
Property value changeChange button text ("Save" → "Save Draft")Translation update or UX revision
Control relocationMove a field from one form group to anotherAnnotation change in CDS view
New required fieldAdd a mandatory field before the Save actionBusiness requirement change via transport
Table column reorderSwap column positions in SmartTableUser personalization or annotation change

Running the Experiment

# 1. Verify baseline passes
npx playwright test tests/e2e/po-edit.spec.ts
# ✓ All passed

# 2. Introduce breakage (done manually in SAP customizing or by editing the spec)
# Example: change selector from text='Save' to text='SaveDraft' in the spec
sed -i "s/text: 'Save'/text: 'SaveDraft'/" tests/e2e/po-edit.spec.ts

# 3. Verify it fails
npx playwright test tests/e2e/po-edit.spec.ts
# ✗ 1 failed

# 4. Run the Healer
npx playwright-praman heal --spec tests/e2e/po-edit.spec.ts

# 5. Verify it passes again
npx playwright test tests/e2e/po-edit.spec.ts
# ✓ All passed

# 6. Review what the healer changed
git diff tests/e2e/po-edit.spec.ts

Healing Accuracy Matrix

Fill this out during the POC:

Breakage TypeAuto-Healed?Time to HealNotes
ID change
Property value change
Control relocation
New required field
Table column reorder

Scoring Criteria

MetricTargetHow to Measure
Overall healing rate>80% (4 of 5 breakage types)Healing accuracy matrix
Healing time<3 min average per failureWall-clock measurement
Selector qualityHealer produces semantic selectors, not fragile IDsCode review
No false fixes0 cases where healer "fixes" by removing assertionsDiff review
Confidence reportingHealer reports confidence level per fixCheck healer output

Scenario 9: OData Assertions

Why this is a differentiator: Most test tools validate what the UI shows. Praman also validates what the backend returns. OData assertions let you verify that the data displayed in a SmartTable actually matches the OData response, that a Create operation sent the correct payload, and that batch requests are properly structured. This catches bugs that UI-only assertions miss entirely.

Agent-First: OData-Aware Plan

# Plan with OData tracing enabled
npx playwright-praman plan \
--url "$SAP_URL#PurchaseOrder-manage" \
--auth .auth/sap-session.json \
--odata-trace \
--output tests/plans/po-odata-assertions.plan.json

The --odata-trace flag tells the planner to capture OData request/response pairs alongside UI interactions, producing richer assertion targets in the plan.

OData Assertion Patterns

import { test, expect } from 'playwright-praman';

test.describe('OData assertions', () => {
test('verify list data matches OData response', async ({ ui5, page }) => {
await page.goto(`${process.env.SAP_BASE_URL}#PurchaseOrder-manage`);
await ui5.waitForUI5();

// Read data directly from the UI5 OData model (browser-side, no HTTP)
const orders = await ui5.odata.getModelData('/PurchaseOrderSet');
expect(orders.length).toBeGreaterThan(0);

// Verify specific entity properties
const firstOrder = orders[0];
expect(firstOrder).toHaveProperty('PurchaseOrder');
expect(firstOrder).toHaveProperty('Supplier');
expect(firstOrder).toHaveProperty('CompanyCode');
expect(firstOrder.CompanyCode).toBe('1000');
});

test('intercept and validate OData Create payload', async ({ ui5, page }) => {
const odataRequests: { method: string; url: string; body: string }[] = [];

// Intercept OData calls
page.on('request', (req) => {
if (req.url().includes('/sap/opu/odata/') && req.method() === 'POST') {
odataRequests.push({
method: req.method(),
url: req.url(),
body: req.postData() ?? '',
});
}
});

await page.goto(`${process.env.SAP_BASE_URL}#PurchaseOrder-create`);
await ui5.waitForUI5();

// Fill form and save
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', bindingPath: { path: 'Supplier' } },
'100001',
);
await ui5.press({ controlType: 'sap.m.Button', properties: { text: 'Save' } });
await ui5.waitForUI5();

// Assert the OData payload sent to the backend
expect(odataRequests.length).toBeGreaterThan(0);
const createRequest = odataRequests.find((r) => r.url.includes('PurchaseOrderSet'));
expect(createRequest).toBeDefined();

const payload = JSON.parse(createRequest!.body);
expect(payload.Supplier).toBe('100001');
});

test('validate OData entity count matches table row count', async ({ ui5, page }) => {
await page.goto(`${process.env.SAP_BASE_URL}#PurchaseOrder-manage`);
await ui5.waitForUI5();

await ui5.press({ controlType: 'sap.m.Button', properties: { text: 'Go' } });
await ui5.waitForUI5();

// Get count from OData model
const odataCount = await ui5.odata.getEntityCount('/PurchaseOrderSet');

// Get count from UI table
const table = await ui5.control({
controlType: 'sap.ui.comp.smarttable.SmartTable',
});
const uiCount = await ui5.table.getRowCount(table);

// They must match — if they don't, there's a data binding bug
expect(uiCount).toBe(odataCount);
});
});

OData Trace Reporter

Add the OData trace reporter to get performance analytics alongside test results:

// playwright.config.ts
import { ODataTraceReporter } from 'playwright-praman/reporters';

export default defineConfig({
reporter: [['html'], [ODataTraceReporter, { outputDir: 'reports/odata' }]],
});

Output in reports/odata/odata-trace.json:

{
"traces": [
{
"method": "GET",
"url": "/sap/opu/odata/sap/API_PURCHASEORDER_PROCESS_SRV/PurchaseOrderSet",
"entitySet": "PurchaseOrderSet",
"status": 200,
"duration": 340,
"size": 12480
}
],
"aggregates": {
"PurchaseOrderSet": {
"GET": { "count": 12, "avgDuration": 340, "maxDuration": 1200, "errors": 0 }
}
}
}

Scoring Criteria

MetricTargetHow to Measure
Model data accessCan read OData model entities from browserui5.odata.getModelData() returns data
Request interceptionCan capture and assert OData payloadsPOST assertion test passes
UI/OData consistencyTable row count matches OData $countConsistency test passes
Trace reporter outputJSON report with per-entity-set aggregatesCheck reports/odata/odata-trace.json
CSRF handlingCreate/Update operations auto-fetch CSRF tokenPOST succeeds without manual token management

POC Scorecard

After completing all 9 scenarios, fill in this scorecard. Each scenario is scored 1-5:

ScoreMeaning
5Agent handled end-to-end, no manual intervention
4Agent handled >80%, minor manual touch-up
3Agent handled ~50%, significant manual effort
2Agent produced a starting point, mostly manual
1Agent could not handle, fully manual

Scorecard Template

#ScenarioAgent Score (1-5)Manual EffortStability (20 runs)Notes
1Fiori Elements app
2Smart Tables/Forms
3Procurement workflow
4Role-based authorization
5Transport-induced UI drift
6CI parallelization
7Flaky network conditions
8AI healing quality
9OData assertions
Weighted Total/45

Weighting by Business Priority

Not all scenarios are equally important to every organization. Assign weights based on your context:

PriorityTypical ScenariosWeight
Must-HaveFiori Elements (#1), Smart Controls (#2), Workflows (#3)2x
CriticalTransport Drift (#5), CI (#6), AI Healing (#8)1.5x
ImportantAuth (#4), Network (#7), OData (#9)1x

Go/No-Go Thresholds:

  • Strong Go: Weighted score > 75% (34/45) — Praman handles your SAP landscape well
  • Conditional Go: Weighted score 55-75% (25-34) — viable with investment in manual test engineering alongside agents
  • No-Go: Weighted score < 55% (below 25) — significant gaps, re-evaluate after next Praman release

Decision Framework: What to Present to Leadership

After the 4-week POC, your report should answer these questions:

1. Productivity Multiplier

"How much faster is test creation with Praman agents vs. manual Playwright?"

Measure: time to produce a passing spec for the same app, agent-assisted vs. manual.

2. Maintenance Cost Reduction

"After a transport cycle, how many tests required manual intervention?"

Measure: Scenario #5 results — healer fix rate and time.

3. Coverage Velocity

"How many apps can we cover in a sprint?"

Extrapolate from POC: if the team produced X specs in 4 weeks across 9 scenarios, project the rate for the full app portfolio.

4. CI Feasibility

"Can we run the full suite in under 30 minutes?"

Measure: Scenario #6 sharding results — extrapolate from POC suite size to target suite size.

5. Risk Assessment

"What are the biggest technical risks?"

Document: which scenarios scored below 3, what workarounds were needed, what Praman features are immature.


Quick Start: Day 1 Checklist

If you want to get started today, here is the minimum viable POC setup:

# 1. Create project
mkdir sap-poc && cd sap-poc
npm init -y

# 2. Install Praman + Playwright
npm install playwright-praman @playwright/test
npx playwright install chromium

# 3. Initialize (scaffolds config, agents, auth setup)
npx playwright-praman init

# 4. Configure SAP credentials
cat > .env << 'EOF'
SAP_BASE_URL=https://your-sap.example.com
SAP_USERNAME=TEST_USER
SAP_PASSWORD=your-password
SAP_CLIENT=100
EOF

# 5. Run seed test to verify connectivity
npx playwright test --project=agent-seed-test

# 6. Point the planner at your first Fiori app
npx playwright-praman plan \
--url "$SAP_BASE_URL/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html#YourApp-display" \
--auth .auth/sap-session.json \
--output tests/plans/first-app.plan.json

# 7. Generate and run
npx playwright-praman generate --plan tests/plans/first-app.plan.json --output tests/e2e/first-app.spec.ts
npx playwright test tests/e2e/first-app.spec.ts

You will have a running test against your real SAP system in under an hour.


Praman is open-source under the Apache-2.0 license. Docs · npm · GitHub · Discord