Skip to main content

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.

BehaviorPlaywright page.locator()Praman ui5.control()
DOM presenceWaits for element in DOMWaits for control in UI5 registry
VisibilityWaits for element visible (actionability)Prefers visible controls (preferVisibleControls)
UI5 stabilityNo awarenessAuto-waits for waitForUI5Stable() (pending requests, timeouts, promises)
RetryBuilt-in auto-retry on locatorsMulti-strategy discovery chain (cache, direct-ID, RecordReplay, registry scan)
Timeoutexpect.timeout / actionTimeoutcontrolDiscoveryTimeout (default 10s) + ui5WaitTimeout (default 30s)
Network idleNo built-in conceptBlocks WalkMe, analytics, overlay scripts automatically

What waitForUI5Stable() Does​

Before every ui5.control() call, Praman ensures:

  1. The UI5 bootstrap has completed (core libraries loaded)
  2. All pending XMLHttpRequest / fetch calls have settled
  3. All JavaScript setTimeout / setInterval callbacks have fired
  4. The OData model has no pending requests
  5. 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​

  1. Use unique test data: The testData fixture generates UUIDs and timestamps to avoid conflicts
  2. Avoid shared state: Do not rely on a specific PO number across parallel workers
  3. Lock management: Use flpLocks to handle SM12 locks -- auto-cleanup prevents stale locks
  4. 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​

FeatureVanilla PlaywrightWith Praman
UI5 control registry accessManual page.evaluate()Built-in ui5.control()
UI5 stability waitingManual polling loopsAutomatic waitForUI5Stable()
OData model accessManual page.evaluate()ui5.odata.getModelData()
SmartField handlingFragile inner-control CSScontrolType + properties
FLP navigationManual URL construction9 typed methods
AuthenticationCustom login scripts6 built-in strategies
UI5 assertionsGeneric expect only10 custom matchers
Error recoveryUnstructured errorsStructured codes + suggestions
Analytics blockingManual route interceptionAutomatic request interceptor
Test dataManual setup/teardownTemplate generation + auto-cleanup