Business Process Examples
- Test Purchase Order creation via classic GUI (ME21N) and Fiori Elements
- Test Sales Order creation via VA01 and Fiori Elements
- Test Journal Entry posting via FB50 and Fiori (F0718)
- Run a full Purchase-to-Pay (P2P) cross-process end-to-end flow
- Apply data cleanup strategies for repeatable test runs
Complete end-to-end test examples for core SAP business processes. Each example is runnable with Praman fixtures and demonstrates real-world patterns for Purchase Orders, Sales Orders, Journal Entries, and cross-process P2P flows.
Purchase Order — ME21N / Fiori
Classic GUI (ME21N via Fiori Launchpad)
import { test, expect } from 'playwright-praman';
test.describe('Purchase Order Creation (ME21N)', () => {
test('create standard purchase order', async ({ ui5, ui5Navigation }) => {
await test.step('navigate to Create Purchase Order app', async () => {
await ui5Navigation.navigateToApp('PurchaseOrder-create');
});
await test.step('fill header data', async () => {
await ui5.fill({ controlType: 'sap.m.Input', id: /supplierInput/ }, '100001');
await ui5.fill({ controlType: 'sap.m.Input', id: /purchOrgInput/ }, '1000');
await ui5.fill({ controlType: 'sap.m.Input', id: /companyCodeInput/ }, '1000');
});
await test.step('add line item', async () => {
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Add Item' } });
await ui5.fill({ controlType: 'sap.m.Input', id: /materialInput/ }, 'MAT-001');
await ui5.fill({ controlType: 'sap.m.Input', id: /quantityInput/ }, '100');
await ui5.fill({ controlType: 'sap.m.Input', id: /plantInput/ }, '1000');
});
await test.step('save and verify', async () => {
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Save' } });
const messageStrip = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
await expect(messageStrip).toHaveUI5Text(/Purchase Order \d+ created/);
});
});
});
Fiori Elements — Manage Purchase Orders
import { test, expect } from 'playwright-praman';
test.describe('Manage Purchase Orders (Fiori Elements)', () => {
test('create PO via Fiori Elements object page', async ({ ui5, ui5Navigation, fe }) => {
await test.step('navigate to list report', async () => {
await ui5Navigation.navigateToApp('PurchaseOrder-manage');
});
await test.step('create new entry', async () => {
await fe.listReport.clickButton('Create');
});
await test.step('fill header fields', async () => {
await ui5.fill({ id: /Supplier/ }, '100001');
await ui5.fill({ id: /PurchasingOrganization/ }, '1000');
await ui5.fill({ id: /CompanyCode/ }, '1000');
await ui5.fill({ id: /PurchasingGroup/ }, '001');
});
await test.step('add line item via table', async () => {
await fe.objectPage.clickButton('Create');
await ui5.fill({ id: /Material/ }, 'MAT-001');
await ui5.fill({ id: /OrderQuantity/ }, '100');
await ui5.fill({ id: /Plant/ }, '1000');
});
await test.step('save and verify', async () => {
await fe.objectPage.clickSave();
const messageStrip = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
await expect(messageStrip).toHaveUI5Text(/Purchase Order \d+ created/);
});
});
});
Sales Order — VA01 / Fiori
Classic GUI (VA01 via Fiori Launchpad)
import { test, expect } from 'playwright-praman';
test.describe('Sales Order Creation (VA01)', () => {
test('create standard sales order', async ({ ui5, ui5Navigation }) => {
await test.step('navigate to Create Sales Order app', async () => {
await ui5Navigation.navigateToApp('SalesOrder-create');
});
await test.step('fill order type and org data', async () => {
await ui5.fill(
{ controlType: 'sap.m.Input', id: /orderTypeInput/ },
'OR', // Standard Order
);
await ui5.fill({ controlType: 'sap.m.Input', id: /salesOrgInput/ }, '1000');
await ui5.fill({ controlType: 'sap.m.Input', id: /distChannelInput/ }, '10');
await ui5.fill({ controlType: 'sap.m.Input', id: /divisionInput/ }, '00');
});
await test.step('fill customer and item data', async () => {
await ui5.fill({ controlType: 'sap.m.Input', id: /soldToPartyInput/ }, 'CUST-001');
await ui5.fill({ controlType: 'sap.m.Input', id: /materialInput/ }, 'MAT-100');
await ui5.fill({ controlType: 'sap.m.Input', id: /quantityInput/ }, '50');
});
await test.step('save and verify', async () => {
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Save' } });
const message = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
await expect(message).toHaveUI5Text(/Sales Order \d+ created/);
});
});
});
Fiori Elements — Manage Sales Orders
import { test, expect } from 'playwright-praman';
test.describe('Manage Sales Orders (Fiori Elements)', () => {
test('create sales order via object page', async ({ ui5, ui5Navigation, fe }) => {
await test.step('open app and create', async () => {
await ui5Navigation.navigateToApp('SalesOrder-manage');
await fe.listReport.clickButton('Create');
});
await test.step('fill header', async () => {
await ui5.fill({ id: /SalesOrderType/ }, 'OR');
await ui5.fill({ id: /SoldToParty/ }, 'CUST-001');
await ui5.fill({ id: /SalesOrganization/ }, '1000');
await ui5.fill({ id: /DistributionChannel/ }, '10');
});
await test.step('add line items', async () => {
await fe.objectPage.clickButton('Create');
await ui5.fill({ id: /Material/ }, 'MAT-100');
await ui5.fill({ id: /RequestedQuantity/ }, '50');
});
await test.step('save and capture order number', async () => {
await fe.objectPage.clickSave();
const messageStrip = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
await expect(messageStrip).toHaveUI5Text(/Sales Order \d+ created/);
});
});
});
Journal Entry — FB50 / Fiori
Classic GUI (FB50 via Fiori Launchpad)
import { test, expect } from 'playwright-praman';
test.describe('Journal Entry (FB50)', () => {
test('post G/L account journal entry', async ({ ui5, ui5Navigation }) => {
await test.step('navigate to Post Journal Entry', async () => {
await ui5Navigation.navigateToApp('JournalEntry-create');
});
await test.step('fill document header', async () => {
await ui5.fill({ controlType: 'sap.m.DatePicker', id: /documentDatePicker/ }, '2025-01-15');
await ui5.fill({ controlType: 'sap.m.Input', id: /companyCodeInput/ }, '1000');
await ui5.fill({ controlType: 'sap.m.Input', id: /currencyInput/ }, 'USD');
});
await test.step('add debit line', async () => {
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Add Row' } });
await ui5.fill(
{ controlType: 'sap.m.Input', id: /glAccountInput--0/ },
'400000', // Revenue account
);
await ui5.fill({ controlType: 'sap.m.Input', id: /debitAmountInput--0/ }, '1000.00');
});
await test.step('add credit line', async () => {
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Add Row' } });
await ui5.fill(
{ controlType: 'sap.m.Input', id: /glAccountInput--1/ },
'113100', // Bank account
);
await ui5.fill({ controlType: 'sap.m.Input', id: /creditAmountInput--1/ }, '1000.00');
});
await test.step('post and verify', async () => {
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Post' } });
const message = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
await expect(message).toHaveUI5Text(/Document \d+ posted/);
});
});
});
Fiori — Post Journal Entries (F0718)
import { test, expect } from 'playwright-praman';
test.describe('Post Journal Entries Fiori (F0718)', () => {
test('create and post journal entry', async ({ ui5, ui5Navigation }) => {
await test.step('navigate to Post Journal Entries', async () => {
await ui5Navigation.navigateToIntent({ semanticObject: 'JournalEntry', action: 'create' });
});
await test.step('fill header and line items', async () => {
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', id: /CompanyCode/ },
'1000',
);
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', id: /DocumentDate/ },
'2025-01-15',
);
// Debit line
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', id: /GLAccount.*0/ },
'400000',
);
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', id: /AmountInTransCrcy.*0/ },
'1000.00',
);
// Credit line
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', id: /GLAccount.*1/ },
'113100',
);
await ui5.fill(
{ controlType: 'sap.ui.comp.smartfield.SmartField', id: /AmountInTransCrcy.*1/ },
'-1000.00',
);
});
await test.step('post document', async () => {
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Post' } });
const message = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
await expect(message).toHaveUI5Text(/Document \d+ posted/);
});
});
});
Purchase-to-Pay (P2P) Cross-Process E2E
The P2P flow spans multiple SAP modules: procurement, goods receipt, invoice verification, and payment. This tutorial demonstrates a complete end-to-end test that exercises the full chain.
import { test, expect } from 'playwright-praman';
test.describe('Purchase-to-Pay E2E Flow', () => {
let poNumber: string;
let grDocNumber: string;
let invoiceNumber: string;
test('complete P2P cycle', async ({ ui5, ui5Navigation, fe }) => {
await test.step('Step 1: Create Purchase Order', async () => {
await ui5Navigation.navigateToIntent({ semanticObject: 'PurchaseOrder', action: 'create' });
await ui5.fill({ id: /Supplier/ }, '100001');
await ui5.fill({ id: /PurchasingOrganization/ }, '1000');
await ui5.fill({ id: /CompanyCode/ }, '1000');
await fe.objectPage.clickButton('Create');
await ui5.fill({ id: /Material/ }, 'MAT-001');
await ui5.fill({ id: /OrderQuantity/ }, '10');
await ui5.fill({ id: /Plant/ }, '1000');
await fe.objectPage.clickSave();
const successMsg = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
const text = await successMsg.getText();
const match = text.match(/Purchase Order (\d+)/);
poNumber = match?.[1] ?? '';
expect(poNumber).toBeTruthy();
});
await test.step('Step 2: Post Goods Receipt (MIGO)', async () => {
await ui5Navigation.navigateToIntent({ semanticObject: 'GoodsReceipt', action: 'create' });
await ui5.fill({ controlType: 'sap.m.Input', id: /poNumberInput/ }, poNumber);
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Load' } });
// Verify items auto-populated
const table = await ui5.control({ controlType: 'sap.m.Table', id: /itemsTable/ });
await expect(table).toHaveUI5RowCount(1);
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Post' } });
const grMsg = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
const grText = await grMsg.getText();
const grMatch = grText.match(/Document (\d+)/);
grDocNumber = grMatch?.[1] ?? '';
expect(grDocNumber).toBeTruthy();
});
await test.step('Step 3: Create Invoice (MIRO)', async () => {
await ui5Navigation.navigateToIntent({ semanticObject: 'SupplierInvoice', action: 'create' });
await ui5.fill({ controlType: 'sap.m.Input', id: /poReferenceInput/ }, poNumber);
await ui5.fill({ controlType: 'sap.m.Input', id: /invoiceAmountInput/ }, '5000.00');
await ui5.fill({ controlType: 'sap.m.DatePicker', id: /invoiceDatePicker/ }, '2025-02-01');
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Post' } });
const invMsg = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
const invText = await invMsg.getText();
const invMatch = invText.match(/Invoice (\d+)/);
invoiceNumber = invMatch?.[1] ?? '';
expect(invoiceNumber).toBeTruthy();
});
await test.step('Step 4: Verify Payment Run (F110)', async () => {
await ui5Navigation.navigateToApp('PaymentRun-display');
await fe.listReport.setFilter('Supplier', '100001');
await fe.listReport.setFilter('Status', 'Paid');
await fe.listReport.search();
});
await test.step('Step 5: Verify complete document flow', async () => {
await ui5Navigation.navigateToIntent(
{ semanticObject: 'PurchaseOrder', action: 'display' },
{ PurchaseOrder: poNumber },
);
await ui5.click({
controlType: 'sap.m.IconTabFilter',
properties: { key: 'documentFlow' },
});
const flowTable = await ui5.control({ controlType: 'sap.m.Table', id: /docFlowTable/ });
await expect(flowTable).toHaveUI5RowCount(3); // PO + GR + Invoice
});
});
});
Key Patterns in the P2P Example
| Pattern | Why |
|---|---|
test.step() for each business step | Clear trace output, easy to pinpoint failures |
| Capture document numbers between steps | Enables cross-referencing across documents |
expect(poNumber).toBeTruthy() after capture | Fail fast if a prior step silently failed |
| Navigate by semantic object + action | Resilient to FLP configuration changes |
| Verify document flow at the end | Confirms SAP linked all documents correctly |
Data Cleanup Strategies
For repeatable tests, clean up created data after the test run:
test.afterAll(async ({ ui5 }) => {
// Option 1: Use OData to delete test data
await ui5.odata.deleteEntity('/PurchaseOrders', poNumber);
// Option 2: Use an SAP cleanup BAPI via RFC
// (Requires backend RFC connection setup)
// Option 3: Flag for batch cleanup
// Store document numbers for nightly cleanup jobs
});
See the Gold Standard Test Pattern for a complete template that includes data cleanup, error handling, and all best practices.
FAQ
Can I run these examples against a real SAP system?
Yes, but you need to update the control IDs and semantic objects to match your specific SAP configuration. The selector patterns (controlType + id RegExp) shown here are based on standard SAP Fiori apps. Your system may use different IDs depending on custom views, extensions, and UI5 versions. Use the Praman CLI or browser DevTools to discover the actual control IDs on your system.
How do I capture document numbers created during a test?
After a save action, find the success message strip and extract the number with a regex:
const msg = await ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Success' },
});
const text = await msg.getText();
const match = text.match(/Document (\d+)/);
const docNumber = match?.[1] ?? '';
expect(docNumber).toBeTruthy();
Store the number in a variable declared at the describe scope so subsequent steps and
cleanup can reference it.
Should I run P2P tests sequentially or in parallel?
Run P2P and other cross-process tests sequentially (workers: 1 in Playwright config).
These tests create and reference data across multiple SAP transactions, so parallel execution
would cause document number conflicts and race conditions. Single-process tests (like
creating a standalone PO) can run in parallel if they use unique test data.
- Gold Standard Test Pattern → — Reference template with error handling, cleanup, and checklist
- Navigation → — All 11 FLP navigation methods used in these examples
- Custom Matchers → — Assert UI5 control state after each business step
- Selector Reference → — Build resilient selectors for your SAP controls