Migration from Vanilla Playwright
Already using Playwright for web testing? This guide shows when and how to layer Praman on top of your existing Playwright tests for SAP UI5 applications.
When to Use page.locator() vs ui5.control()​
Praman does not replace Playwright -- it extends it. Use each where it makes sense.
Use page.locator() When​
- Targeting non-UI5 elements (plain HTML, third-party widgets, SAP UI5 Web Components)
- Working with static DOM that does not change between UI5 versions
- Testing login pages before the UI5 runtime has loaded
- Interacting with iframes, file uploads, or browser dialogs
- Asserting on CSS properties or visual layout
Use ui5.control() When​
- Targeting SAP UI5 controls (sap.m.Button, sap.m.Input, sap.ui.table.Table, etc.)
- Control DOM IDs are generated and change across versions or deployments
- You need OData binding path or i18n text matching
- You need UI5-level assertions (value state, enabled, editable, binding)
- Working with SmartFields that wrap inner controls dynamically
- Navigating the Fiori Launchpad shell
Decision Tree​
Is the element a SAP UI5 control?
YES -> Does its DOM structure change across UI5 versions/themes?
YES -> Use ui5.control() (stable contract via UI5 registry)
NO -> Either works, but ui5.control() is safer long-term
NO -> Use page.locator() (standard Playwright)
Auto-Waiting Differences​
Both Playwright and Praman auto-wait, but they wait for different things.
| Behavior | Playwright page.locator() | Praman ui5.control() |
|---|---|---|
| DOM presence | Waits for element in DOM | Waits for control in UI5 registry |
| Visibility | Waits for element visible (actionability) | Prefers visible controls (preferVisibleControls) |
| UI5 stability | No awareness | Auto-waits for waitForUI5Stable() (pending requests, timeouts, promises) |
| Retry | Built-in auto-retry on locators | Multi-strategy discovery chain (cache, direct-ID, RecordReplay, registry scan) |
| Timeout | expect.timeout / actionTimeout | controlDiscoveryTimeout (default 10s) + ui5WaitTimeout (default 30s) |
| Network idle | No built-in concept | Blocks WalkMe, analytics, overlay scripts automatically |
What waitForUI5Stable() Does​
Before every ui5.control() call, Praman ensures:
- The UI5 bootstrap has completed (core libraries loaded)
- All pending
XMLHttpRequest/fetchcalls have settled - All JavaScript
setTimeout/setIntervalcallbacks have fired - The OData model has no pending requests
- A 500ms DOM settle period has elapsed
This eliminates the need for page.waitForTimeout() (which is banned in Praman) and
page.waitForLoadState('networkidle') (which is unreliable for SPAs).
Hybrid Playwright + Praman Test​
The most practical migration approach is a hybrid test that uses both APIs in the same file.
Praman's test and expect are extended versions of Playwright's -- your existing locators
continue to work.
import { test, expect } from 'playwright-praman';
test('hybrid PW + Praman test', async ({ page, ui5, ui5Navigation, sapAuth }) => {
// Step 1: Playwright handles login (before UI5 loads)
await test.step('Login via SAP login page', async () => {
await page.goto(process.env.SAP_BASE_URL!);
// Plain Playwright locators for the login form
await page.locator('#USERNAME_FIELD input').fill('TESTUSER');
await page.locator('#PASSWORD_FIELD input').fill('secret');
await page.locator('#LOGIN_LINK').click();
// Wait for redirect to FLP
await page.waitForURL('**/FioriLaunchpad*');
});
// Step 2: Praman takes over once UI5 is loaded
await test.step('Navigate to PO app', async () => {
await ui5Navigation.navigateToApp('PurchaseOrder-manage');
});
// Step 3: Use ui5.control() for UI5 controls
await test.step('Verify table loaded', async () => {
const table = await ui5.control({
controlType: 'sap.m.Table',
id: 'poTable',
});
await expect(table).toHaveUI5RowCount({ min: 1 });
});
// Step 4: Mix both in the same step when needed
await test.step('Check page title and create button', async () => {
// Playwright for the browser title
await expect(page).toHaveTitle(/Purchase Orders/);
// Praman for the UI5 button
const createBtn = await ui5.control({
controlType: 'sap.m.Button',
properties: { text: 'Create' },
});
await expect(createBtn).toBeUI5Enabled();
});
// Step 5: Playwright for screenshots and traces
await test.step('Capture evidence', async () => {
await page.screenshot({ path: 'evidence/po-list.png', fullPage: true });
});
});
Converting Existing Tests Incrementally​
You do not need to rewrite everything at once. The recommended approach:
Phase 1: Drop-In Replacement (5 minutes)​
Change your import and keep everything else the same.
// Before
import { test, expect } from '@playwright/test';
// After -- Praman re-exports everything from @playwright/test
import { test, expect } from 'playwright-praman';
// All your existing locator-based tests continue to work unchanged
test('existing test', async ({ page }) => {
await page.goto('/my-app');
await page.locator('#myButton').click();
await expect(page.locator('#result')).toHaveText('Done');
});
Phase 2: Add UI5 Fixtures Gradually​
Start using ui5 for new assertions alongside existing locators.
import { test, expect } from 'playwright-praman';
test('gradual adoption', async ({ page, ui5 }) => {
await page.goto('/my-app');
// Old way -- still works
await page.locator('#myButton').click();
// New way -- more resilient for UI5 controls
const result = await ui5.control({
controlType: 'sap.m.Text',
properties: { text: 'Done' },
});
await expect(result).toHaveUI5Text('Done');
});
Phase 3: Replace Brittle Selectors​
Find tests that break on UI5 upgrades and convert their selectors.
// Before: Brittle CSS selector that breaks on theme/version changes
await page.locator('.sapMBtnInner.sapMBtnEmphasized').click();
// After: Stable UI5 selector via control registry
await ui5.click({
controlType: 'sap.m.Button',
properties: { type: 'Emphasized' },
});
Phase 4: Adopt Navigation and Auth Fixtures​
Replace manual navigation and login scripts with built-in fixtures.
// Before: Manual hash navigation
await page.goto(
'https://my-system.com/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html#PurchaseOrder-manage',
);
// After: Typed navigation
await ui5Navigation.navigateToApp('PurchaseOrder-manage');
Parallel Execution Guidance​
Playwright parallelism works the same with Praman. Each worker gets its own browser context with isolated cookies, storage, and UI5 state.
Workers and SAP Sessions​
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
// Each worker authenticates independently via storageState
workers: process.env.CI ? 2 : 1,
// SAP systems often struggle with high parallelism
// Start with 1-2 workers and increase carefully
fullyParallel: false, // Run tests within a file sequentially
retries: 1,
projects: [
{
name: 'setup',
testMatch: /auth-setup\.ts/,
teardown: 'teardown',
},
{
name: 'teardown',
testMatch: /auth-teardown\.ts/,
},
{
name: 'chromium',
dependencies: ['setup'],
use: { storageState: '.auth/sap-session.json' },
},
],
});
Parallel Safety Tips​
- Use unique test data: The
testDatafixture generates UUIDs and timestamps to avoid conflicts - Avoid shared state: Do not rely on a specific PO number across parallel workers
- Lock management: Use
flpLocksto handle SM12 locks -- auto-cleanup prevents stale locks - Session limits: SAP systems may limit concurrent sessions per user -- use separate test users per worker if needed
import { test } from 'playwright-praman';
test('parallel-safe test', async ({ ui5, testData }) => {
// Generate unique test data per run
const po = testData.generate({
documentNumber: '{{uuid}}',
createdAt: '{{timestamp}}',
vendor: '100001',
});
await ui5.fill({ id: 'vendorInput' }, po.vendor);
// Each worker gets its own unique data -- no conflicts
});
What You Gain Over Vanilla Playwright​
| Feature | Vanilla Playwright | With Praman |
|---|---|---|
| UI5 control registry access | Manual page.evaluate() | Built-in ui5.control() |
| UI5 stability waiting | Manual polling loops | Automatic waitForUI5Stable() |
| OData model access | Manual page.evaluate() | ui5.odata.getModelData() |
| SmartField handling | Fragile inner-control CSS | controlType + properties |
| FLP navigation | Manual URL construction | 9 typed methods |
| Authentication | Custom login scripts | 6 built-in strategies |
| UI5 assertions | Generic expect only | 10 custom matchers |
| Error recovery | Unstructured errors | Structured codes + suggestions |
| Analytics blocking | Manual route interception | Automatic request interceptor |
| Test data | Manual setup/teardown | Template generation + auto-cleanup |