Skip to main content

OData Operations

Praman provides two approaches to OData data access: model operations (browser-side, via ui5.odata) and HTTP operations (server-side, via ui5.odata). This page covers both, including V2 vs V4 differences and the ODataTraceReporter.

Two Approaches​

ApproachAccess PathUse Case
Model operationsBrowser-side UI5 modelReading data the UI already loaded
HTTP operationsDirect HTTP to OData serviceCRUD operations, test data setup/teardown

Model Operations (Browser-Side)​

Model operations query the UI5 OData model that is already loaded in the browser. No additional HTTP requests are made — you are reading the same data the UI5 application is displaying.

getModelData​

Reads data at any model path. Returns the full object tree.

const orders = await ui5.odata.getModelData('/PurchaseOrders');
console.log(orders.length); // Number of loaded POs

const singlePO = await ui5.odata.getModelData("/PurchaseOrders('4500000001')");
console.log(singlePO.Vendor); // '100001'

getModelProperty​

Reads a single property value from the model.

const vendor = await ui5.odata.getModelProperty("/PurchaseOrders('4500000001')/Vendor");
const amount = await ui5.odata.getModelProperty("/PurchaseOrders('4500000001')/NetAmount");

getEntityCount​

Counts entities in a set. Reads from the model, not from $count.

const count = await ui5.odata.getEntityCount('/PurchaseOrders');
expect(count).toBeGreaterThan(0);

waitForODataLoad​

Polls the model until data is available at the given path. Useful after navigation when OData requests are still in flight.

await ui5.odata.waitForODataLoad('/PurchaseOrders');
// Data is now available
const orders = await ui5.odata.getModelData('/PurchaseOrders');

Default timeout: 15 seconds. Polling interval: 100ms.

hasPendingChanges​

Checks if the model has unsaved changes (dirty state).

await ui5.fill({ id: 'vendorInput' }, '100002');
const dirty = await ui5.odata.hasPendingChanges();
expect(dirty).toBe(true);

await ui5.click({ id: 'saveBtn' });
const clean = await ui5.odata.hasPendingChanges();
expect(clean).toBe(false);

fetchCSRFToken​

Fetches a CSRF token from an OData service URL. Needed for write operations via HTTP.

const token = await ui5.odata.fetchCSRFToken('/sap/opu/odata/sap/API_PO_SRV');
// Use token in custom HTTP requests

Named Model Support​

By default, model operations query the component's default model. To query a named model:

const data = await ui5.odata.getModelData('/Products', { modelName: 'productModel' });

HTTP Operations (Server-Side)​

HTTP operations perform direct OData CRUD operations against the service endpoint using Playwright's request context. These are independent of the browser — useful for test data setup, teardown, and backend verification.

queryEntities​

Performs an HTTP GET with OData query parameters.

const orders = await ui5.odata.queryEntities('/sap/opu/odata/sap/API_PO_SRV', 'PurchaseOrders', {
filter: "Status eq 'A'",
select: 'PONumber,Vendor,Amount',
expand: 'Items',
orderby: 'PONumber desc',
top: 10,
skip: 0,
});

createEntity​

Performs an HTTP POST with automatic CSRF token management.

const newPO = await ui5.odata.createEntity('/sap/opu/odata/sap/API_PO_SRV', 'PurchaseOrders', {
Vendor: '100001',
PurchOrg: '1000',
CompanyCode: '1000',
Items: [{ Material: 'MAT-001', Quantity: 10, Unit: 'EA' }],
});
console.log(newPO.PONumber); // Server-generated PO number

updateEntity​

Performs an HTTP PATCH with CSRF token and optional ETag for optimistic concurrency.

await ui5.odata.updateEntity('/sap/opu/odata/sap/API_PO_SRV', 'PurchaseOrders', "'4500000001'", {
Status: 'B',
Note: 'Updated by test',
});

deleteEntity​

Performs an HTTP DELETE with CSRF token.

await ui5.odata.deleteEntity('/sap/opu/odata/sap/API_PO_SRV', 'PurchaseOrders', "'4500000001'");

callFunctionImport​

Calls an OData function import (action).

const result = await ui5.odata.callFunctionImport('/sap/opu/odata/sap/API_PO_SRV', 'ApprovePO', {
PONumber: '4500000001',
});

OData V2 vs V4 Differences​

AspectOData V2OData V4
Model classsap.ui.model.odata.v2.ODataModelsap.ui.model.odata.v4.ODataModel
URL convention/sap/opu/odata/sap/SERVICE_SRV/sap/opu/odata4/sap/SERVICE/srvd_a2x/...
Batch$batch with changesets$batch with JSON:API format
CSRF tokenFetched via HEAD request with X-CSRF-Token: FetchSame mechanism
Deep insertSupported via navigation propertiesSupported natively
Filter syntax$filter=Status eq 'A'$filter=Status eq 'A' (same)

Praman's model operations work with both V2 and V4 models transparently — the model class handles the protocol differences. HTTP operations use the URL conventions of your service.

OData Trace Reporter​

The ODataTraceReporter captures all OData HTTP requests during test runs and generates a performance trace. See the Reporters guide for configuration.

The trace output includes per-entity-set statistics:

{
"entitySets": {
"PurchaseOrders": {
"GET": { "count": 12, "avgDuration": 340, "maxDuration": 1200, "errors": 0 },
"POST": { "count": 2, "avgDuration": 890, "maxDuration": 1100, "errors": 0 },
"PATCH": { "count": 1, "avgDuration": 450, "maxDuration": 450, "errors": 0 }
},
"Vendors": {
"GET": { "count": 3, "avgDuration": 120, "maxDuration": 200, "errors": 0 }
}
},
"totalRequests": 18,
"totalErrors": 0,
"totalDuration": 8400
}

Complete Example​

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

test('verify OData model state after form edit', async ({ ui5, ui5Navigation }) => {
await ui5Navigation.navigateToApp('PurchaseOrder-manage');

// Wait for initial data load
await ui5.odata.waitForODataLoad('/PurchaseOrders');

// Read model data
const orders = await ui5.odata.getModelData('/PurchaseOrders');
expect(orders.length).toBeGreaterThan(0);

// Navigate to first PO
await ui5.click({
controlType: 'sap.m.ColumnListItem',
ancestor: { id: 'poTable' },
});

// Edit a field
await ui5.click({ id: 'editBtn' });
await ui5.fill({ id: 'noteField' }, 'Test note');

// Verify model has pending changes
const dirty = await ui5.odata.hasPendingChanges();
expect(dirty).toBe(true);

// Save
await ui5.click({ id: 'saveBtn' });

// Verify model is clean
const clean = await ui5.odata.hasPendingChanges();
expect(clean).toBe(false);
});

test('seed and cleanup test data via HTTP', async ({ ui5 }) => {
// Setup: create test PO via direct HTTP
const po = await ui5.odata.createEntity('/sap/opu/odata/sap/API_PO_SRV', 'PurchaseOrders', {
Vendor: '100001',
PurchOrg: '1000',
CompanyCode: '1000',
});

// ... run test steps ...

// Cleanup: delete the test PO
await ui5.odata.deleteEntity(
'/sap/opu/odata/sap/API_PO_SRV',
'PurchaseOrders',
`'${po.PONumber}'`,
);
});