A comprehensive E2E testing toolbox for Neptune DXP applications. Provides reusable page objects, helpers, and utilities for testing both SAP Edition and Open Edition launchpads.
- Overview
- Features
- Installation
- Architecture
- Quick Start
- API Reference
- OAuth Flows
- Version Resolution
- Known Issues
- Contributing
The DXP E2E Toolbox abstracts away the complexity of testing Neptune DXP mobile and web applications. It provides:
- Edition-aware page objects for SAP Edition and Open Edition
- Version-aware implementations that adapt to different DXP versions
- OAuth flow management for Azure, Okta, and BTP IAS authentication
- Context/WebView switching for mobile app testing
- App lifecycle management for restart, background, and terminate operations
graph TB
subgraph "Your E2E Test Specs"
SPEC[Test Specification]
end
subgraph "@neptune-software/dxp-e2e-toolbox"
TF[ToolboxFactory]
LP[Launchpad]
OFM[OAuthFlowManager]
CU[ContextUtil]
ALU[AppLifecycleUtil]
WHU[WindowHandleUtil]
TF --> LP
LP --> OFM
OFM --> CU
OFM --> WHU
ALU --> CU
end
subgraph "WebDriverIO + wdi5"
WDIO[WebDriverIO Browser]
WDI5[wdi5 UI5 Bridge]
end
SPEC --> TF
SPEC --> OFM
SPEC --> ALU
LP --> WDIO
LP --> WDI5
CU --> WDIO
ALU --> WDIO
| Edition | Status | Notes |
|---|---|---|
| SAP Edition | âś… Full Support | Versions 22, 22.10, 23+ |
| Open Edition | âś… Full Support | All versions |
- Factory Pattern - Create launchpad instances with automatic version resolution
- Fluent API - Chainable methods for readable test code
- Type Safety - Full TypeScript support with UI5 types
- Resilient Operations - Built-in retries and error handling
- OAuth Integration - Azure AD, Okta, BTP IAS support
| Platform | Status | Notes |
|---|---|---|
| Android | âś… Stable | BrowserStack and local Appium |
| iOS | Known WebView/wdi5 flakiness issues | |
| Browser | âś… Stable | Chrome, Firefox, Safari |
npm install @neptune-software/dxp-e2e-toolbox{
"peerDependencies": {
"webdriverio": "^9",
"wdio-ui5-service": "^3",
"expect-webdriverio": "^5"
}
}classDiagram
class ToolboxFactory {
+createLaunchpad(options) BaseLaunchpad
+createSapEditionLaunchpad(options) BaseLaunchpad
+createOpenEditionLaunchpad(options) BaseLaunchpad
}
class BaseLaunchpad {
+login(username, password)
+clickLogin()
+setPincode(pin, confirm)
+enterPincode(pin)
+openTile(guid)
+getCurrentScreen()
+openUserMenu()
+addAnotherUser()
}
class SapEditionLaunchpad {
+navigateToLaunchpad()
+getAllTiles()
}
class OpenEditionLaunchpad {
+open()
+navigateToLaunchpad()
}
class OAuthFlowManager {
+performLogin(options) OAuthLoginResult
+prepareForLogin(options)
+completeLogin(browserMode)
}
class ContextUtil {
+ensureInWebview(options)
+ensureInNative()
+switchToMainWebview()
+waitForInAppBrowserToClose()
}
class AppLifecycleUtil {
+restartApp(options)
+terminateApp()
+backgroundApp()
+activateApp()
}
BaseLaunchpad <|-- SapEditionLaunchpad
BaseLaunchpad <|-- OpenEditionLaunchpad
ToolboxFactory --> BaseLaunchpad
OAuthFlowManager --> ContextUtil
AppLifecycleUtil --> ContextUtil
The toolbox uses a version resolver to pick the correct implementation based on DXP version:
flowchart TD
V[Version: 24.14.1] --> R1{Has v24 impl?}
R1 -->|No| R2{Has v23 impl?}
R1 -->|Yes| I24[LaunchpadV24]
R2 -->|Yes| I23[LaunchpadV23]
R2 -->|No| R3{Has v22.10 impl?}
R3 -->|Yes| I22_10[LaunchpadV22_10]
R3 -->|No| R4{Has v22 impl?}
R4 -->|Yes| I22[LaunchpadV22]
R4 -->|No| IB[BaseLaunchpad]
import { ToolboxFactory, OAuthFlowManager, initToolbox } from "@neptune-software/dxp-e2e-toolbox";
describe("My Test", () => {
before(async () => {
// Initialize toolbox with edition and version
await initToolbox();
});
it("should login and open a tile", async () => {
// Create a launchpad instance
const launchpad = await ToolboxFactory.createLaunchpad({
launchpadName: "MY_LAUNCHPAD",
});
// Perform basic login
await launchpad.login("username", "password");
// Set pincode
await launchpad.setPincode("1234", "1234");
await launchpad.enterPincode("1234");
// Open a tile
await launchpad.openTile("TILE_GUID");
});
});import { OAuthFlowManager } from "@neptune-software/dxp-e2e-toolbox";
it("should login with Azure", async () => {
const oauthManager = OAuthFlowManager.getInstance();
const result = await oauthManager.performLogin({
provider: "azure",
browserMode: "native", // or "inappbrowser"
launchpadName: "MY_OAUTH_LAUNCHPAD",
credentials: {
email: "user@example.com",
password: "secret",
staySignedIn: false,
},
});
expect(result.success).toBe(true);
});Static factory for creating edition/version-specific launchpad instances.
// Auto-detect edition from environment
const launchpad = await ToolboxFactory.createLaunchpad({
launchpadName: "MY_LAUNCHPAD",
});
// Explicit SAP Edition
const sapLaunchpad = await ToolboxFactory.createSapEditionLaunchpad({
launchpadName: "MY_LAUNCHPAD",
version: "23",
});
// Explicit Open Edition
const openLaunchpad = await ToolboxFactory.createOpenEditionLaunchpad({
launchpadName: "MY_LAUNCHPAD",
});Page object for interacting with Neptune launchpad.
| Method | Description |
|---|---|
login(username, password) |
Login with basic authentication |
clickLogin() |
Click the login button (for OAuth flows) |
setPincode(pin, confirm) |
Set a new pincode |
enterPincode(pin) |
Enter an existing pincode |
getCurrentScreen() |
Get the current screen identifier |
openTile(guid) |
Open a tile by GUID |
closeTile(guid) |
Close a tile by GUID |
openUserMenu() |
Open the user menu |
closeUserMenu() |
Close the user menu |
addAnotherUser() |
Add another user to the app |
selectUser(index) |
Select a user from the list |
lock() |
Lock the screen |
Manages OAuth authentication flows for Azure, Okta, and BTP IAS.
interface PerformOAuthLoginOptions {
provider: "azure" | "okta" | "btp-ias" | "sap-basic" | "custom";
credentials: {
email: string;
password: string;
staySignedIn?: boolean;
rememberMe?: boolean;
keepMeSignedIn?: boolean;
};
browserMode?: "native" | "inappbrowser";
launchpadName?: string;
timeout?: number;
customOAuthUrlPatterns?: string[];
isOAuthPageCallback?: (url: string) => Promise<boolean>;
customLoginHandler?: () => Promise<void>;
}
interface OAuthLoginResult {
success: boolean;
autoTriggered: boolean;
browserMode: "native" | "inappbrowser";
loginContext?: Context;
error?: string;
}Utility for managing WebView and native contexts in mobile apps.
| Method | Description |
|---|---|
ensureInWebview(options) |
Switch to main app WebView |
ensureInNative() |
Switch to native context |
switchToMainWebview() |
Find and switch to main WebView |
waitForInAppBrowserToClose(timeout) |
Wait for InAppBrowser to close |
getCurrentContext() |
Get current context info |
getAllContexts() |
Get all available contexts |
Utility for managing app lifecycle (restart, background, etc.).
| Method | Description |
|---|---|
restartApp(options) |
Restart the app |
terminateApp() |
Terminate the app |
backgroundApp(duration) |
Background the app |
activateApp() |
Activate/foreground the app |
getAppId() |
Get the app package/bundle ID |
waitForSplashScreenGone() |
Wait for splash screen to disappear |
Restart Modes:
| Mode | Behavior |
|---|---|
fresh |
Relaunch app - preserves localStorage, shows initial screen |
resume |
Background and activate - resumes exactly where you were |
clean |
Terminate and activate - clears ALL data |
sequenceDiagram
participant App as Mobile App
participant Native as Native Context
participant Safari as Safari/Chrome
participant IdP as Identity Provider
App->>App: Click Login Button
App->>Native: iOS Permission Dialog
Native->>Safari: Open OAuth URL
Safari->>IdP: Load Login Page
IdP->>IdP: User Enters Credentials
IdP->>Safari: Redirect with Token
Safari->>App: Return to App
App->>App: Re-inject wdi5
App->>App: Continue Flow
sequenceDiagram
participant App as Mobile App
participant IAB as InAppBrowser
participant IdP as Identity Provider
App->>App: Click Login Button
App->>IAB: Open OAuth URL
IAB->>IdP: Load Login Page
IdP->>IdP: User Enters Credentials
IdP->>IAB: Redirect with Token
IAB->>IAB: Close Automatically
App->>App: Cookie Sync (iOS)
App->>App: Continue Flow
| Provider | URL Patterns |
|---|---|
| Azure AD | login.microsoftonline.com, login.windows.net |
| Okta | *.okta.com, *.oktapreview.com |
| BTP IAS | accounts.sap.com, *.authentication.* |
The toolbox automatically selects the correct implementation based on DXP version:
| Version Pattern | Implementation |
|---|---|
24.*, 23.* |
LaunchpadV23 |
22.10.* |
LaunchpadV22_10 |
22.* |
LaunchpadV22 |
* (fallback) |
BaseLaunchpad |
| Variable | Description | Default |
|---|---|---|
NEPTUNE_EDITION |
Edition type (sap-edition or open-edition) |
sap-edition |
NEPTUNE_VERSION |
DXP version (e.g., 24.14.1) |
23 |
iOS testing with WebViews and wdi5 can be unreliable due to:
- Safari Remote Debugger instability - The debugger connection can become corrupted after context switches
- wdi5 injection failures - After OAuth flows or app restarts, wdi5 may fail to reinject
- Context switching timing - iOS WebViews can take longer to stabilize than Android
Workarounds implemented:
- Aggressive context reset after OAuth flows
- Multiple wdi5 injection retries
- Warmup execute calls to prime the debugger connection
- Extended timeouts for iOS operations
Recommendation: For production CI/CD, prioritize Android testing. iOS testing should be considered experimental.
On iOS with InAppBrowser OAuth flows, the Neptune framework may open a secondary InAppBrowser for cookie synchronization after PIN entry. This can sometimes show a blank screen if:
- Network connectivity issues
- Auth token expiration
- Framework configuration issues
The toolbox includes detection and timeout handling for this scenario.
# Clone the repository
git clone https://github.com/neptune-software/dxp-e2e-toolbox.git
# Install dependencies
npm install
# Build
npm run build
# Link for local development
npm link- Create a new file in
src/sap-edition/versions/orsrc/open-edition/versions/ - Extend the base launchpad class
- Register in
ToolboxFactory.initResolvers()
// src/sap-edition/versions/launchpad-v25.ts
import { SapEditionLaunchpad } from "../launchpad.js";
export class SapEditionLaunchpadV25 extends SapEditionLaunchpad {
// Override methods as needed for v25 changes
}
// In ToolboxFactory.initResolvers():
this.sapEditionResolver.register("25", async () => {
const { SapEditionLaunchpadV25 } = await import("../sap-edition/versions/launchpad-v25.js");
return SapEditionLaunchpadV25;
});- Create a new file in
src/oauth/ - Extend
OAuthProviderbase class - Add to
OAuthFlowManager.executeOAuthLogin()
// src/oauth/my-provider-login.ts
import { OAuthProvider } from "./oauth-provider.js";
export class MyProviderLogin extends OAuthProvider {
public async login(options: MyProviderLoginOptions): Promise<void> {
// Implement login flow
}
}MIT