# Sap Fiori Testing

> >

- **Type:** Skill
- **Install:** `agentstack add skill-efeumutaslan-sap-skills-sap-fiori-testing`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [efeumutaslan](https://agentstack.voostack.com/s/efeumutaslan)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [efeumutaslan](https://github.com/efeumutaslan)
- **Source:** https://github.com/efeumutaslan/SAP-SKILLS/tree/main/skills/sap-fiori-testing

## Install

```sh
agentstack add skill-efeumutaslan-sap-skills-sap-fiori-testing
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# SAP Fiori Testing, Accessibility & Modern UI

## Related Skills
- `sap-testing-quality` — Cross-cutting SAP testing strategy
- `sap-devops-cicd` — UI test automation in pipelines
- `sap-build-apps` — Alternative low-code UI approach

## Quick Start

**Choose your test framework:**

| Framework | Level | Speed | Best For |
|-----------|-------|-------|----------|
| **QUnit** | Unit | Fast | Controller logic, formatters |
| **OPA5** | Integration | Medium | UI interaction flows, navigation |
| **wdi5** | E2E | Slow | Full app testing, cross-app |
| **UIVeri5** | Visual/E2E | Slow | Visual regression, screenshots |

**Quick wdi5 setup:**

```bash
npm init wdi5@latest
# Follow prompts: select UI5 app, choose test runner
# Generates: wdio.conf.js + test structure
```

## Core Concepts

### UI5 Test Pyramid
```
         /\
        /  \     E2E (wdi5/UIVeri5)
       /    \    - Full app in browser
      /------\   - Real backend or mock server
     /        \
    / OPA5     \ Integration
   /   Tests    \ - UI interaction + navigation
  /--------------\ - Mock server for data
 /                \
/ QUnit Unit Tests \ Unit
/                    \ - Pure logic, no DOM
/____________________\
```

### wdi5 Architecture
```
Test Script ──► wdi5 Bridge ──► UI5 Control API
    │               │               │
 WebdriverIO    Injects JS      sap.ui.test.*
 (Selenium/     into browser    control selectors
  Playwright)
```

### OPA5 Key Concepts
| Concept | Role | Example |
|---------|------|---------|
| **Page Object** | Encapsulates page interactions | `onTheMainPage.iClickCreate()` |
| **Journey** | Test scenario (sequence of actions) | `opaTest("Create order", ...)` |
| **Arrangement** | Setup state | `Given.iStartMyApp()` |
| **Action** | User interaction | `When.onTheList.iSelectItem("001")` |
| **Assertion** | Verify outcome | `Then.onTheDetail.iSeeTitle("Order 001")` |

## Common Patterns

### Pattern 1: wdi5 Test with UI5 Selectors

```javascript
// test/e2e/specs/order.test.js
const { wdi5 } = require('wdio-ui5-service');

describe('Order Management', () => {
  before(async () => {
    await browser.url('#/Orders');
    await browser.asControl({
      selector: { controlType: 'sap.m.Page', viewName: 'orders.List' }
    });
  });

  it('should display order list', async () => {
    const table = await browser.asControl({
      selector: {
        controlType: 'sap.m.Table',
        viewName: 'orders.List',
        id: 'orderTable'
      }
    });
    const items = await table.getItems();
    expect(items.length).toBeGreaterThan(0);
  });

  it('should create new order', async () => {
    // Click Create button
    const createBtn = await browser.asControl({
      selector: {
        controlType: 'sap.m.Button',
        viewName: 'orders.List',
        properties: { text: 'Create' }
      }
    });
    await createBtn.press();

    // Fill form
    const customerInput = await browser.asControl({
      selector: {
        controlType: 'sap.m.Input',
        viewName: 'orders.Create',
        id: 'customerInput'
      }
    });
    await customerInput.setValue('CUST001');

    const amountInput = await browser.asControl({
      selector: {
        controlType: 'sap.m.Input',
        viewName: 'orders.Create',
        id: 'amountInput'
      }
    });
    await amountInput.setValue('1500.00');

    // Save
    const saveBtn = await browser.asControl({
      selector: {
        controlType: 'sap.m.Button',
        viewName: 'orders.Create',
        properties: { type: 'Emphasized' }
      }
    });
    await saveBtn.press();

    // Verify success message
    const msgStrip = await browser.asControl({
      selector: {
        controlType: 'sap.m.MessageStrip',
        properties: { type: 'Success' }
      }
    });
    const text = await msgStrip.getText();
    expect(text).toContain('created successfully');
  });
});
```

```javascript
// wdio.conf.js
exports.config = {
  specs: ['./test/e2e/specs/**/*.test.js'],
  maxInstances: 1,
  capabilities: [{
    browserName: 'chrome',
    'goog:chromeOptions': {
      args: ['--headless', '--no-sandbox', '--disable-gpu']
    }
  }],
  baseUrl: 'http://localhost:8080',
  services: ['ui5'],
  ui5: {
    path: { webapp: 'webapp' },
    url: 'http://localhost:8080/index.html'
  },
  framework: 'mocha',
  mochaOpts: { timeout: 60000 }
};
```

### Pattern 2: OPA5 Integration Test

```javascript
// test/integration/pages/OrderList.js
sap.ui.define([
  'sap/ui/test/Opa5',
  'sap/ui/test/actions/Press',
  'sap/ui/test/actions/EnterText',
  'sap/ui/test/matchers/Properties',
  'sap/ui/test/matchers/AggregationLengthEquals'
], function (Opa5, Press, EnterText, Properties, AggregationLengthEquals) {
  'use strict';

  Opa5.createPageObjects({
    onTheOrderListPage: {
      actions: {
        iSearchFor: function (sQuery) {
          return this.waitFor({
            id: 'searchField',
            viewName: 'orders.List',
            actions: new EnterText({ text: sQuery }),
            errorMessage: 'Search field not found'
          });
        },
        iPressCreateButton: function () {
          return this.waitFor({
            controlType: 'sap.m.Button',
            viewName: 'orders.List',
            matchers: new Properties({ text: 'Create' }),
            actions: new Press(),
            errorMessage: 'Create button not found'
          });
        },
        iSelectFirstItem: function () {
          return this.waitFor({
            controlType: 'sap.m.ColumnListItem',
            viewName: 'orders.List',
            actions: new Press(),
            errorMessage: 'No list items found'
          });
        }
      },
      assertions: {
        iShouldSeeTheTable: function () {
          return this.waitFor({
            id: 'orderTable',
            viewName: 'orders.List',
            success: function () { Opa5.assert.ok(true, 'Table is visible'); },
            errorMessage: 'Table not found'
          });
        },
        theTableShouldHaveEntries: function (iCount) {
          return this.waitFor({
            id: 'orderTable',
            viewName: 'orders.List',
            matchers: new AggregationLengthEquals({
              name: 'items', length: iCount
            }),
            success: function () {
              Opa5.assert.ok(true, 'Table has ' + iCount + ' entries');
            },
            errorMessage: 'Table does not have ' + iCount + ' entries'
          });
        }
      }
    }
  });
});
```

```javascript
// test/integration/OrderJourney.js
sap.ui.define([
  'sap/ui/test/opaQunit',
  './pages/OrderList'
], function (opaTest) {
  'use strict';

  QUnit.module('Order Management');

  opaTest('Should see order list on start', function (Given, When, Then) {
    Given.iStartMyApp();
    Then.onTheOrderListPage.iShouldSeeTheTable();
  });

  opaTest('Should filter orders by search', function (Given, When, Then) {
    When.onTheOrderListPage.iSearchFor('CUST001');
    Then.onTheOrderListPage.theTableShouldHaveEntries(3);
  });

  opaTest('Should navigate to create page', function (Given, When, Then) {
    When.onTheOrderListPage.iPressCreateButton();
    Then.onTheCreatePage.iShouldSeeTheForm();
    Then.iTeardownMyApp();
  });
});
```

### Pattern 3: Fiori Elements OPA5 Test

```javascript
// test/integration/FEListReportJourney.js
sap.ui.define([
  'sap/ui/test/opaQunit',
  'sap/fe/test/ListReport',
  'sap/fe/test/ObjectPage'
], function (opaTest, ListReport, ObjectPage) {
  'use strict';

  var oListReport = new ListReport({ entitySet: 'Orders' });
  var oObjectPage = new ObjectPage({ entitySet: 'Orders' });

  QUnit.module('Fiori Elements - List Report');

  opaTest('Table loads with data', function (Given, When, Then) {
    Given.iStartMyFLPApp('orders-manage');
    Then.onTheListReport.iSeeTheTable();
    Then.onTheListReport.iCheckRows(10);
  });

  opaTest('Filter by status', function (Given, When, Then) {
    When.onTheListReport.iOpenFilterBar();
    When.onTheListReport.iFilterByField('Status', 'Open');
    When.onTheListReport.iExecuteFilter();
    Then.onTheListReport.iCheckRows(5);
  });

  opaTest('Navigate to detail', function (Given, When, Then) {
    When.onTheListReport.iPressRow(0);
    Then.onTheObjectPage.iSeeThisPage();
    Then.onTheObjectPage.iSeeHeaderTitle('ORD-001');
    Then.iTeardownMyApp();
  });
});
```

### Pattern 4: WCAG 2.1 Accessibility Checklist

```javascript
// Accessibility patterns for Fiori apps

// 1. Labels for all inputs
// BAD:
new sap.m.Input({ placeholder: "Enter name" });
// GOOD:
new sap.m.Label({ text: "Customer Name", labelFor: "nameInput" });
new sap.m.Input({ id: "nameInput" });

// 2. ARIA for custom controls
new sap.m.GenericTile({
  header: "Revenue",
  ariaLabel: "Revenue tile showing 1.5 million euros",
  press: function() { /* navigate */ }
});

// 3. High contrast support — use semantic colors
// BAD: custom CSS colors
// GOOD: use UI5 semantic classes
// .sapMObjStatusActive (standard active state)
// sap.ui.core.IconColor.Positive / Negative / Critical

// 4. Keyboard navigation
// Ensure all interactive elements are reachable via Tab
// Use sap.m.Table not custom HTML for data grids
// Test with screen reader (JAWS, NVDA, VoiceOver)

// 5. Color-independent information
// BAD: status shown only by color
// GOOD: status shown by color + icon + text
new sap.m.ObjectStatus({
  text: "Approved",
  state: "Success",
  icon: "sap-icon://accept"
});
```

### Pattern 5: UI5 TypeScript Migration

```typescript
// webapp/controller/OrderList.controller.ts
import Controller from "sap/ui/core/mvc/Controller";
import JSONModel from "sap/ui/model/json/JSONModel";
import Filter from "sap/ui/model/Filter";
import FilterOperator from "sap/ui/model/FilterOperator";
import MessageToast from "sap/m/MessageToast";
import Event from "sap/ui/base/Event";
import Table from "sap/m/Table";
import ListBinding from "sap/ui/model/ListBinding";

/**
 * @namespace orders.controller
 */
export default class OrderList extends Controller {
  public onInit(): void {
    const oViewModel = new JSONModel({
      busy: false,
      orderCount: 0
    });
    this.getView()?.setModel(oViewModel, "view");
  }

  public onSearch(oEvent: Event): void {
    const sQuery = (oEvent.getParameter("query") as string) || "";
    const aFilters: Filter[] = [];

    if (sQuery) {
      aFilters.push(new Filter("CustomerName", FilterOperator.Contains, sQuery));
    }

    const oTable = this.byId("orderTable") as Table;
    const oBinding = oTable.getBinding("items") as ListBinding;
    oBinding.filter(aFilters);
  }

  public onCreatePress(): void {
    this.getOwnerComponent()?.getRouter().navTo("create");
  }

  public async onRefresh(): Promise {
    const oTable = this.byId("orderTable") as Table;
    const oBinding = oTable.getBinding("items") as ListBinding;

    try {
      await oBinding.requestRefresh();
      MessageToast.show("Data refreshed");
    } catch (error) {
      MessageToast.show("Refresh failed");
    }
  }
}
```

```json
// tsconfig.json for UI5 TypeScript
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "node",
    "skipLibCheck": true,
    "allowJs": true,
    "strict": true,
    "strictNullChecks": true,
    "baseUrl": ".",
    "paths": {
      "orders/*": ["webapp/*"]
    },
    "types": ["@openui5/types"]
  },
  "include": ["webapp/**/*.ts"]
}
```

### Pattern 6: UI5 Web Components Integration

```html

  import "@ui5/webcomponents/dist/Button.js";
  import "@ui5/webcomponents/dist/Table.js";
  import "@ui5/webcomponents/dist/Input.js";
  import "@ui5/webcomponents-fiori/dist/ShellBar.js";
  import "@ui5/webcomponents-fiori/dist/SideNavigation.js";

  
    Order ID
  
  
    Customer
  
  
    ORD-001
    Acme Corp
  

```

```jsx
// React + UI5 Web Components
import { ShellBar, Table, TableColumn, TableRow, TableCell } from '@ui5/webcomponents-react';

function OrderList({ orders }) {
  return (
    <>
      
      
          Order ID
          Customer
          Amount
        
      }>
        {orders.map(order => (
          
            {order.id}
            {order.customer}
            {order.amount}
          
        ))}
      
    
  );
}
```

## Error Catalog

| Error | Context | Root Cause | Fix |
|-------|---------|------------|-----|
| `wdi5: Control not found` | wdi5 selector | Wrong controlType or viewName | Use UI5 Inspector to get correct selector |
| `OPA5: Timeout` | waitFor expired | Element not rendered in time | Increase `timeout`; check if element is conditional |
| `OPA5: Ambiguous match` | Multiple controls match | Selector too broad | Add `id`, `viewName`, or more specific `matchers` |
| `TS2307: Cannot find module` | TypeScript import | Missing `@openui5/types` or wrong path | Install types: `npm i -D @openui5/types` |
| `wdi5: Session not created` | Browser start failed | ChromeDriver version mismatch | Update chromedriver or use `services: ['chromedriver']` |
| `FE test: Page not found` | Fiori Elements OPA | Wrong entitySet or app ID | Check `manifest.json` routing config |
| `a11y: Missing label` | Accessibility audit | Input without associated label | Add `sap.m.Label` with `labelFor` |
| `a11y: Low contrast` | Color contrast check | Custom theme colors too similar | Use SAP theme parameters; test with High Contrast theme |

## Performance Tips

1. **OPA5 over wdi5 for UI logic** — OPA5 runs in-browser (no Selenium overhead); use wdi5 only for cross-app/E2E
2. **Mock server for OPA5** — Use `sap/ui/core/util/MockServer` to eliminate backend dependency
3. **Headless browser** — Run wdi5/UIVeri5 headless in CI/CD (Chrome `--headless`)
4. **Parallel OPA5 suites** — Split OPA test files by module; run in parallel with Karma
5. **Selective test runs** — Use `QUnit.module` filtering to run only changed test suites
6. **UI5 TypeScript** — Compiler catches errors at build time; reduces runtime debugging

## Gotchas

- **wdi5 vs UIVeri5**: wdi5 is community-maintained (active), UIVeri5 is SAP-maintained (slower updates)
- **OPA5 auto-wait**: OPA5 waits for UI5 rendering automatically; don't add manual `setTimeout`
- **Fiori Elements tests**: Use `sap/fe/test/*` page objects, not custom selectors for FE apps
- **TypeScript types**: `@openui5/types` (open-source) vs `@sapui5/types` (requires SAP license)
- **Web Components ≠ UI5**: `@ui5/webcomponents` is framework-agnostic; different API from `sap.m.*` controls
- **Accessibility testing**: Automated tools catch ~30% of issues; manual screen reader testing is essential

## MCP Server Integration

```json
{
  "mcpServers": {
    "ui5-mcp": {
      "command": "npx", "args": ["-y", "@ui5/mcp-server"]
    },
    "fiori-mcp": {
      "command": "npx", "args": ["-y", "@sap-ux/fiori-mcp-server"]
    }
  }
}
```

- **UI5 MCP** (official, 10+ tools): API reference, linting, TS migration guidelines, app scaffolding
- **Fiori MCP** (official): App generation, page management, annotation editing

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [efeumutaslan](https://github.com/efeumutaslan)
- **Source:** [efeumutaslan/SAP-SKILLS](https://github.com/efeumutaslan/SAP-SKILLS)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-efeumutaslan-sap-skills-sap-fiori-testing
- Seller: https://agentstack.voostack.com/s/efeumutaslan
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
