Skip to main content
Version: 1.x

BTP Multi-Tenant Auth

Demonstrates how to handle multi-tenant authentication on SAP BTP Work Zone, including tenant switching, session isolation, and tenant-scoped data verification.

When to Use

  • ISV applications deployed to multiple BTP tenants
  • Multi-tenant SaaS testing where each tenant has its own data context
  • Verifying session cookies are properly scoped per tenant
  • Testing tenant-specific FLP tile catalogs and content

Source

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

test.describe('BTP Work Zone Multi-Tenant Auth', () => {
test('authenticate and verify tenant context', async ({ page, ui5, btpWorkZone }) => {
await test.step('Login to BTP Work Zone via IDP', async () => {
// btpWorkZone fixture handles the full SAML redirect flow
await btpWorkZone.login({
url: process.env['BTP_WORKZONE_URL']!,
username: process.env['SAP_USERNAME']!,
password: process.env['SAP_PASSWORD']!,
tenant: process.env['BTP_TENANT_1']!,
});

await ui5.waitForUI5();
await expect(page).toHaveURL(/site#/);
});

await test.step('Verify tenant-specific FLP content', async () => {
await expect(async () => {
const tile = await ui5.control({
controlType: 'sap.ushell.ui.launchpad.Tile',
});
expect(tile).toBeTruthy();
}).toPass({ timeout: 30_000, intervals: [2000, 5000] });
});

await test.step('Navigate to tenant-specific app', async () => {
await expect(async () => {
await ui5.press({
controlType: 'sap.m.GenericTile',
properties: { header: 'Manage Purchase Orders' },
});
}).toPass({ timeout: 60_000, intervals: [5000, 10_000] });

await ui5.waitForUI5();
});

await test.step('Verify tenant data context via OData', async () => {
await ui5.odata.waitForODataLoad('/PurchaseOrders');
const orders = await ui5.odata.getModelData('/PurchaseOrders');
expect(orders).toBeTruthy();
});
});

test('switch tenant and verify session isolation', async ({ page, ui5, btpWorkZone }) => {
await test.step('Login to first tenant', async () => {
await btpWorkZone.login({
url: process.env['BTP_WORKZONE_URL']!,
username: process.env['SAP_USERNAME']!,
password: process.env['SAP_PASSWORD']!,
tenant: process.env['BTP_TENANT_1']!,
});
await ui5.waitForUI5();
});

await test.step('Switch to second tenant', async () => {
// switchTenant handles logout and re-auth to new tenant
await btpWorkZone.switchTenant({
tenant: process.env['BTP_TENANT_2']!,
username: process.env['SAP_USERNAME']!,
password: process.env['SAP_PASSWORD']!,
});

await ui5.waitForUI5();
await expect(page).toHaveURL(/site#/);
});

await test.step('Verify second tenant has different data', async () => {
await expect(async () => {
await ui5.press({
controlType: 'sap.m.GenericTile',
properties: { header: 'Manage Purchase Orders' },
});
}).toPass({ timeout: 60_000, intervals: [5000, 10_000] });

await ui5.waitForUI5();
await ui5.odata.waitForODataLoad('/PurchaseOrders');
});
});

test('verify session cookies are tenant-scoped', async ({ page, btpWorkZone }) => {
await test.step('Login to tenant', async () => {
await btpWorkZone.login({
url: process.env['BTP_WORKZONE_URL']!,
username: process.env['SAP_USERNAME']!,
password: process.env['SAP_PASSWORD']!,
tenant: process.env['BTP_TENANT_1']!,
});
});

await test.step('Verify session cookies contain tenant subdomain', async () => {
const cookies = await page.context().cookies();
const sessionCookies = cookies.filter(
(c) => c.name.includes('JSESSIONID') || c.name.includes('SAP_SESSIONID'),
);
expect(sessionCookies.length).toBeGreaterThan(0);

for (const cookie of sessionCookies) {
expect(cookie.domain).toContain(process.env['BTP_TENANT_1']!);
}
});

await test.step('Logout and verify session cleanup', async () => {
await btpWorkZone.logout();
const cookies = await page.context().cookies();
const remainingSessions = cookies.filter(
(c) => c.name.includes('JSESSIONID') || c.name.includes('SAP_SESSIONID'),
);
expect(remainingSessions.length).toBe(0);
});
});
});

Key Concepts

  • btpWorkZone fixture -- handles the full BTP Work Zone SAML authentication flow, including IDP redirects
  • btpWorkZone.login() -- authenticates to a specific tenant subdomain
  • btpWorkZone.switchTenant() -- logs out of the current tenant and re-authenticates to a different one
  • btpWorkZone.logout() -- cleans up session cookies and logs out
  • Tenant isolation -- each tenant has its own FLP catalog, data context, and session cookies
  • Cookie scoping -- session cookies (JSESSIONID, SAP_SESSIONID) are scoped to the tenant subdomain

Environment Variables

VariableDescriptionExample
BTP_WORKZONE_URLBTP Work Zone base URLhttps://myapp.launchpad.cfapps.eu10.hana.ondemand.com
BTP_TENANT_1First tenant subdomainacme-dev
BTP_TENANT_2Second tenant subdomainglobex-dev
SAP_USERNAMEIDP credentials (username)[email protected]
SAP_PASSWORDIDP credentials (password)(stored in CI vault)