From Selenium WebDriver
Migrating from Selenium WebDriver (Java, Python, C#, or JavaScript) to Playwright + Praman for SAP Fiori and UI5 web application testing. This guide maps every core Selenium concept to its Praman equivalent, compares configuration, and provides a step-by-step migration path.
- Replace WebDriver protocol, driver management, and Selenium Grid with zero-infrastructure Playwright
- Convert
By.id(),By.xpath(), andBy.css()selectors to UI5-awareUI5Selectorobjects - Eliminate explicit waits and
Thread.sleep()with automaticwaitForUI5Stable() - Migrate Page Object Model classes to Playwright fixtures with dependency injection
- Adopt SAP-specific capabilities that have no Selenium equivalent (auth, FLP navigation, OData)
Quick Comparison
| Aspect | Selenium WebDriver | Praman (Playwright) |
|---|---|---|
| Protocol | WebDriver (W3C) over HTTP | Chrome DevTools Protocol (CDP) / BiDi |
| Selector model | By.id, By.xpath, By.css, By.className | UI5Selector object (controlType, properties, binding) |
| UI5 awareness | None (raw DOM only) | Native UI5 control registry access |
| Auto-waiting | None (manual WebDriverWait + ExpectedConditions) | Built-in waitForUI5Stable() + auto-retry |
| Parallel execution | Selenium Grid / TestNG / pytest-xdist | Native Playwright workers |
| CI/CD integration | Requires Grid or cloud service | Zero infrastructure (npx playwright test) |
| Browser install | Manual driver management (ChromeDriver) | npx playwright install (auto-managed) |
| Language | Java, Python, C#, JavaScript, Ruby, Kotlin | TypeScript (first-class) |
| Test runner | JUnit, TestNG, pytest, NUnit, Mocha | Playwright Test (built-in) |
| Auth management | Manual login scripts | 6 built-in strategies + setup projects |
| Reporting | Allure, ExtentReports (third-party) | Built-in HTML, JSON, JUnit reporters |
| AI integration | None | Built-in capabilities, recipes, agentic handler |
| OData helpers | None | Model-level + HTTP-level CRUD |
| FLP navigation | Manual URL construction | 11 typed navigation methods |
| Trace / debug | Screenshots only | Full trace (DOM snapshots, network, console, video) |
| Page Object Model | Class-based POMs | Playwright fixtures (dependency injection) |
Core API Mapping
Navigation, Discovery, Interaction, and Assertions
// Selenium (Java)
driver.get("https://sap-system.example.com/.../FioriLaunchpad.html#PurchaseOrder-manage");
WebElement button = driver.findElement(By.id("__xmlview0--saveBtn"));
WebElement input = driver.findElement(By.xpath("//input[@aria-label='Vendor']"));
button.click();
input.sendKeys("100001");
String text = driver.findElement(By.id("__xmlview0--statusText")).getText();
assertEquals("Active", text);
assertTrue(driver.findElement(By.id("__xmlview0--saveBtn")).isDisplayed());
// Praman
import { test, expect } from 'playwright-praman';
test('PO overview', async ({ ui5, ui5Navigation }) => {
await ui5Navigation.navigateToApp('PurchaseOrder-manage');
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Save' } });
await ui5.fill({ id: 'vendorInput' }, '100001');
const status = await ui5.control({ id: 'statusText' });
await expect(status).toHaveUI5Text('Active');
await expect(await ui5.control({ id: 'saveBtn' })).toBeUI5Visible();
});
Waiting for Elements
// Selenium (Java) -- manual explicit waits
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("__xmlview0--poTable")));
Thread.sleep(5000); // common anti-pattern
// Praman -- no explicit waits needed
// ui5.control() automatically waits for UI5 bootstrap, pending OData requests,
// JS timeouts/intervals, and DOM stabilization before returning
const table = await ui5.control({ controlType: 'sap.m.Table', id: 'poTable' });
await expect(table).toHaveUI5RowCount({ min: 1 });
Do not carry over Selenium's implicitlyWait pattern by adding page.waitForTimeout() calls in Praman tests.
Praman's ui5.control() automatically waits for the UI5 framework to stabilize (pending OData requests, JS
timeouts, DOM settle) before returning. Adding manual waits defeats this mechanism and slows your tests.
Selector Mapping
Selenium selectors target raw DOM elements. Praman selectors target the UI5 control registry, which survives UI5 version upgrades, theme changes, and DOM restructuring.
| Selenium Selector | Praman UI5Selector Equivalent | Notes |
|---|---|---|
By.id("__xmlview0--myBtn") | { id: 'myBtn' } | Praman strips view prefixes automatically |
By.xpath("//button[@text='Save']") | { controlType: 'sap.m.Button', properties: { text: 'Save' } } | Property matching replaces XPath |
By.cssSelector(".sapMBtn") | { controlType: 'sap.m.Button' } | Control type replaces CSS class matching |
By.className("sapMInputBaseInner") | { controlType: 'sap.m.Input', id: 'myInput' } | Target the control, not its inner DOM |
By.linkText("Purchase Orders") | { controlType: 'sap.m.Link', properties: { text: 'Purchase Orders' } } | Text property replaces link text |
By.xpath("//div[@data-path]") | { bindingPath: { value: '/Vendor/Name' } } | OData binding path matching |
By.xpath with contains() | { id: /partialMatch/ } | RegExp ID matching |
By.xpath with ancestor axis | { ancestor: { controlType: 'sap.m.Panel', id: 'headerPanel' } } | Ancestor selector replaces XPath ancestor axis |
| N/A | { searchOpenDialogs: true } | Dialog-aware search (no Selenium equivalent) |
:::tip Generated IDs
Selenium tests for SAP UI5 apps commonly break because UI5 generates DOM IDs like
__xmlview0--__button3. These IDs change across page reloads, UI5 versions, and theme switches.
Praman's UI5Selector bypasses DOM IDs entirely by querying the UI5 control registry.
:::
Config Mapping
Selenium Capabilities (Java)
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless", "--window-size=1920,1080");
WebDriver driver = new ChromeDriver(options);
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(60));
Praman Config (playwright.config.ts + praman.config.ts)
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 120_000,
retries: 1,
workers: process.env.CI ? 2 : 1,
use: {
baseURL: process.env.SAP_CLOUD_BASE_URL,
headless: true, // --headless
viewport: { width: 1920, height: 1080 }, // --window-size
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'setup', testMatch: /auth-setup\.ts/, teardown: 'teardown' },
{ name: 'teardown', testMatch: /auth-teardown\.ts/ },
{
name: 'chromium',
dependencies: ['setup'],
use: { ...devices['Desktop Chrome'], storageState: '.auth/sap-session.json' },
},
],
});
// praman.config.ts
import { defineConfig } from 'playwright-praman';
export default defineConfig({
logLevel: 'info',
ui5WaitTimeout: 30_000, // Replaces implicitlyWait + WebDriverWait
controlDiscoveryTimeout: 10_000,
interactionStrategy: 'ui5-native',
auth: {
strategy: 'basic',
baseUrl: process.env.SAP_CLOUD_BASE_URL!,
username: process.env.SAP_CLOUD_USERNAME!,
password: process.env.SAP_CLOUD_PASSWORD!,
},
});
Page Object Model to Fixtures
Selenium teams almost universally use the Page Object Model. Praman replaces class-based POMs with Playwright's fixture system -- dependency injection with automatic teardown.
Selenium Page Object (Java)
public class PurchaseOrderPage {
@FindBy(id = "__xmlview0--vendorInput-inner") private WebElement vendorInput;
@FindBy(id = "__xmlview0--saveBtn") private WebElement saveButton;
@FindBy(css = ".sapMMsgStrip") private WebElement messageStrip;
public PurchaseOrderPage(WebDriver d) { PageFactory.initElements(d, this); }
public void fillVendor(String v) { vendorInput.clear(); vendorInput.sendKeys(v); }
public void save() { saveButton.click(); }
public String getMessage() { return messageStrip.getText(); }
}
// Test
PurchaseOrderPage po = new PurchaseOrderPage(driver);
po.fillVendor("100001");
po.save();
assertEquals("PO created", po.getMessage());
Praman Fixture Equivalent
import { test, expect } from 'playwright-praman';
test('create purchase order', async ({ ui5, ui5Navigation, ui5Footer }) => {
await test.step('Navigate', async () => {
await ui5Navigation.navigateToApp('PurchaseOrder-create');
});
await test.step('Fill vendor', async () => {
await ui5.fill({ id: 'vendorInput' }, '100001');
});
await test.step('Save', async () => {
await ui5Footer.clickSave();
});
await test.step('Verify success', async () => {
const message = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
searchOpenDialogs: true,
});
await expect(message).toBeUI5Visible();
});
});
For reusable logic across tests, extract helper functions instead of POM classes.
// helpers/po-helpers.ts
import type { ExtendedUI5Handler } from 'playwright-praman';
export async function fillPOHeader(
ui5: ExtendedUI5Handler,
data: { vendor: string; purchaseOrg: string; companyCode: string },
): Promise<void> {
await ui5.fill({ id: 'vendorInput' }, data.vendor);
await ui5.fill({ id: 'purchOrgInput' }, data.purchaseOrg);
await ui5.fill({ id: 'compCodeInput' }, data.companyCode);
}
Hybrid Approach During Migration
You do not need to rewrite everything at once. Praman's test and expect are extended versions
of Playwright's -- page.locator() works alongside ui5.control() in the same test.
import { test, expect } from 'playwright-praman';
test('hybrid test', async ({ page, ui5, ui5Navigation }) => {
// Playwright handles login (before UI5 loads)
await test.step('Login', async () => {
await page.goto(process.env.SAP_CLOUD_BASE_URL!);
await page.locator('#USERNAME_FIELD input').fill('TESTUSER');
await page.locator('#PASSWORD_FIELD input').fill('secret');
await page.locator('#LOGIN_LINK').click();
await page.waitForURL('**/FioriLaunchpad*');
});
// Praman takes over once UI5 is loaded
await test.step('Navigate and verify', async () => {
await ui5Navigation.navigateToApp('PurchaseOrder-manage');
await expect(page).toHaveTitle(/Purchase Orders/);
const table = await ui5.control({ controlType: 'sap.m.Table', id: 'poTable' });
await expect(table).toHaveUI5RowCount({ min: 1 });
});
});
New Features Only in Praman
Praman includes capabilities that have no Selenium equivalent. These are worth adopting early.
6 Built-In Auth Strategies
No more custom login scripts or cookie injection. Praman supports Basic, BTP SAML, Office 365, Custom, API, and Certificate authentication out of the box.
test('after auth', async ({ ui5Navigation }) => {
await ui5Navigation.navigateToApp('PurchaseOrder-manage');
});
11 FLP Navigation Methods
test('navigation', async ({ ui5Navigation }) => {
await ui5Navigation.navigateToApp('PurchaseOrder-manage');
await ui5Navigation.navigateToTile('Create Purchase Order');
await ui5Navigation.navigateToIntent(
{ semanticObject: 'PurchaseOrder', action: 'create' },
{ plant: '1000' },
);
await ui5Navigation.navigateToHome();
await ui5Navigation.navigateBack();
});
Fiori Elements Helpers
Dedicated APIs for List Report and Object Page patterns -- no more fragile XPath chains.
test('FE list report', async ({ fe }) => {
await fe.listReport.setFilter('Status', 'Active');
await fe.listReport.search();
await fe.listReport.navigateToItem(0);
await fe.objectPage.clickEdit();
await fe.objectPage.clickSave();
});
AI-Powered Test Generation
test('AI discovery', async ({ pramanAI }) => {
const context = await pramanAI.discoverPage({ interactiveOnly: true });
const result = await pramanAI.agentic.generateTest(
'Create a purchase order for vendor 100001',
page,
);
});
Custom UI5 Matchers
10 UI5-specific Playwright matchers -- replacing verbose Selenium assert chains.
const button = await ui5.control({ id: 'saveBtn' });
await expect(button).toBeUI5Enabled();
await expect(button).toHaveUI5Text('Save');
await expect(button).toBeUI5Visible();
OData Model + HTTP Operations
test('OData', async ({ ui5 }) => {
const data = await ui5.odata.getModelData('/PurchaseOrders');
// HTTP-level CRUD with automatic CSRF handling
});
SM12 Lock Management
test('locks', async ({ flpLocks }) => {
await flpLocks.deleteAllLockEntries('TESTUSER');
// Auto-cleanup on teardown
});
Structured Error Codes
Every Praman error includes a machine-readable code, retryable flag, and suggestions[]
array for self-healing tests. Selenium errors are raw WebDriverException messages with stack
traces but no recovery guidance.
Step-by-Step Migration Checklist
- Install dependencies:
npm install playwright-praman @playwright/test - Install browsers:
npx playwright install - Create config files:
praman.config.tsandplaywright.config.ts(see Config Mapping) - Set up auth: Create
tests/auth-setup.tsusing one of the 6 strategies - Convert selectors: Replace
By.id()/By.xpath()/By.css()withUI5Selectorobjects - Remove explicit waits: Delete all
WebDriverWait,Thread.sleep(),implicitlyWait-- Praman auto-waits - Convert Page Objects: Replace POM classes with fixture destructuring and helper functions
- Convert assertions: Replace JUnit/TestNG
assertEqualswith Playwrightexpect+ UI5 matchers - Structure tests: Convert
@Testmethods totest()+test.step()blocks - Adopt navigation: Replace
driver.get()URL strings withui5Navigationmethods - Remove driver management: Delete ChromeDriver setup, Grid config, and teardown code
- Run and verify:
npx playwright test
Team Workflow Transition
From Java/Maven/Grid to TypeScript/npm/Playwright
| Workflow | Selenium Stack | Praman Stack |
|---|---|---|
| Language | Java (+ Kotlin, Python, C#) | TypeScript |
| Build tool | Maven / Gradle | npm |
| Test runner | JUnit / TestNG | Playwright Test (built-in) |
| Test structure | @Test annotated methods in classes | test() functions in .test.ts files |
| Page Objects | Java classes with @FindBy | Playwright fixtures + helper functions |
| Driver management | WebDriverManager / ChromeDriver | npx playwright install (fully automatic) |
| Parallel execution | Selenium Grid + TestNG parallel suite | workers: 4 in playwright.config.ts |
| CI/CD | Grid server + nodes + Docker | npx playwright test (zero infrastructure) |
| Reporting | Allure / ExtentReports plugin | Built-in HTML report (npx playwright show-report) |
| Environment config | Maven profiles / testng.xml | .env files + playwright.config.ts projects |
| Version control | Git (test code) + driver binaries | Git (everything, no binaries) |
| IDE | IntelliJ / Eclipse | VS Code (recommended) / any IDE |
Suggested Team Transition Plan
Week 1-2: Foundation
- Install Node.js, VS Code, and Playwright on developer machines
- Complete the Playwright Primer (2-3 hours per person)
- Set up a Git repository for the test project
Week 3-4: First Tests
- Convert 3-5 Selenium test classes from a single Fiori app to Praman tests
- Set up authentication via setup projects (replaces Selenium login helpers)
- Run tests locally and review the HTML report
Week 5-6: CI Integration
- Add
npx playwright testto your CI/CD pipeline (replaces Grid + Maven Surefire) - Configure
screenshot: 'only-on-failure'andtrace: 'on-first-retry' - Set up environment variables for SAP credentials in CI secrets
Week 7-8: Scale
- Convert remaining Selenium test suites in priority order
- Adopt Fiori Elements helpers (
fe.listReport,fe.objectPage) for standard Fiori apps - Enable parallel execution with 2-4 workers (replaces Selenium Grid)
- Decommission Selenium Grid infrastructure
Role Mapping
| Selenium Role | Praman Equivalent |
|---|---|
| SDET / Automation Engineer | Test Engineer (writes .test.ts files) |
| Grid Administrator | Not needed (no infrastructure to manage) |
| Driver / Browser Manager | Not needed (npx playwright install) |
| Test Architect (POMs) | Same role, designs fixtures and helper modules |
| QA Lead / Manager | Reviews PRs, reads Playwright HTML reports, monitors CI |
FAQ
How does Playwright's architecture differ from Selenium WebDriver?
Selenium communicates with the browser over the W3C WebDriver protocol via HTTP. Each command is a round-trip HTTP request to the driver server (ChromeDriver, GeckoDriver). Playwright uses the Chrome DevTools Protocol (CDP) or WebDriver BiDi over a persistent WebSocket connection. This means Playwright commands are faster, support event-driven waiting, and do not require separate driver binaries.
Can I migrate incrementally or do I need to rewrite all tests at once?
You can migrate incrementally. Start with a few high-value test classes, convert them to Praman tests,
and run both Selenium and Playwright suites in parallel during the transition. Praman supports hybrid
tests where page.locator() and ui5.control() coexist in the same file, so you can convert selectors
one at a time.
Do I still need Selenium Grid for parallel execution?
No. Playwright has native parallel execution via the workers setting in playwright.config.ts. Each
worker runs in its own isolated browser context. There is no grid infrastructure to install, configure,
or maintain. Run npx playwright test on any machine or CI runner.
What happens to my Page Object Model classes?
Playwright uses fixtures instead of class-based Page Object Models. Fixtures provide dependency injection
with automatic setup and teardown. You can extract reusable logic into helper functions (see the
"Page Object Model to Fixtures" section above). The pattern is simpler and avoids the @FindBy
annotation overhead.
- Getting Started -- Install Praman and run your first SAP UI5 test
- Selectors -- Learn the full
UI5Selectorsyntax for targeting controls - Control Interactions -- Click, fill, select, and assert on UI5 controls