agentsclimarketplace

Sap fiori testing

Skill efeumutaslan/SAP-SKILLS/skills/sap-fiori-testing

23 SAP development skills for Claude Code — ABAP, RAP, CAP, Fiori, BTP, HANA, S/4HANA, Integration Suite and more. Agent Skills Specification compatible.

Install
npx -y skills add efeumutaslan/SAP-SKILLS --skill sap-fiori-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

SAP Fiori/UI5 testing, accessibility, and modern UI skill. Use when writing wdi5 E2E tests, OPA5 integration tests, implementing WCAG 2.1 accessibility, using UI5 Web Components, or migrating UI5 to TypeScript. If the user mentions wdi5, OPA5, Fiori test, UI5 accessibility, WCAG, or UI5 TypeScript migration, use this skill.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

16.0 KB, as published. Nobody here has run it

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:

FrameworkLevelSpeedBest For
QUnitUnitFastController logic, formatters
OPA5IntegrationMediumUI interaction flows, navigation
wdi5E2ESlowFull app testing, cross-app
UIVeri5Visual/E2ESlowVisual regression, screenshots

Quick wdi5 setup:

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

ConceptRoleExample
Page ObjectEncapsulates page interactionsonTheMainPage.iClickCreate()
JourneyTest scenario (sequence of actions)opaTest("Create order", ...)
ArrangementSetup stateGiven.iStartMyApp()
ActionUser interactionWhen.onTheList.iSelectItem("001")
AssertionVerify outcomeThen.onTheDetail.iSeeTitle("Order 001")

Common Patterns

Pattern 1: wdi5 Test with UI5 Selectors

// 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');
  });
});
// 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

// 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'
          });
        }
      }
    }
  });
});
// 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

// 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

// 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

// 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<void> {
    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");
    }
  }
}
// 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

<!-- Using UI5 Web Components in standalone/React/Vue apps -->
<script type="module">
  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";
</script>

<ui5-shellbar primary-title="Order Management" show-notifications>
</ui5-shellbar>

<ui5-table>
  <ui5-table-column slot="columns">
    <ui5-label>Order ID</ui5-label>
  </ui5-table-column>
  <ui5-table-column slot="columns">
    <ui5-label>Customer</ui5-label>
  </ui5-table-column>
  <ui5-table-row>
    <ui5-table-cell><ui5-label>ORD-001</ui5-label></ui5-table-cell>
    <ui5-table-cell><ui5-label>Acme Corp</ui5-label></ui5-table-cell>
  </ui5-table-row>
</ui5-table>
// React + UI5 Web Components
import { ShellBar, Table, TableColumn, TableRow, TableCell } from '@ui5/webcomponents-react';

function OrderList({ orders }) {
  return (
    <>
      <ShellBar primaryTitle="Order Management" />
      <Table columns={
        <>
          <TableColumn><span>Order ID</span></TableColumn>
          <TableColumn><span>Customer</span></TableColumn>
          <TableColumn><span>Amount</span></TableColumn>
        </>
      }>
        {orders.map(order => (
          <TableRow key={order.id}>
            <TableCell><span>{order.id}</span></TableCell>
            <TableCell><span>{order.customer}</span></TableCell>
            <TableCell><span>{order.amount}</span></TableCell>
          </TableRow>
        ))}
      </Table>
    </>
  );
}

Error Catalog

ErrorContextRoot CauseFix
wdi5: Control not foundwdi5 selectorWrong controlType or viewNameUse UI5 Inspector to get correct selector
OPA5: TimeoutwaitFor expiredElement not rendered in timeIncrease timeout; check if element is conditional
OPA5: Ambiguous matchMultiple controls matchSelector too broadAdd id, viewName, or more specific matchers
TS2307: Cannot find moduleTypeScript importMissing @openui5/types or wrong pathInstall types: npm i -D @openui5/types
wdi5: Session not createdBrowser start failedChromeDriver version mismatchUpdate chromedriver or use services: ['chromedriver']
FE test: Page not foundFiori Elements OPAWrong entitySet or app IDCheck manifest.json routing config
a11y: Missing labelAccessibility auditInput without associated labelAdd sap.m.Label with labelFor
a11y: Low contrastColor contrast checkCustom theme colors too similarUse 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

{
  "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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.