Skip to main content

Accessibility Testing

How to use @axe-core/playwright alongside Praman to validate accessibility (a11y) in SAP UI5 and Fiori applications. Covers ARIA roles in UI5 controls, common violations, and integration patterns.

Setup​

Install the axe-core Playwright integration:

npm install --save-dev @axe-core/playwright

Basic Accessibility Scan​

import { test, expect } from 'playwright-praman';
import AxeBuilder from '@axe-core/playwright';

test.describe('Accessibility - Purchase Order List', () => {
test('list report page has no critical a11y violations', async ({ ui5Navigation, page }) => {
await test.step('navigate to list report', async () => {
await ui5Navigation.navigateToIntent('#PurchaseOrder-manage');
});

await test.step('run axe accessibility scan', async () => {
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();

// Attach full results to the test report for review
await test.info().attach('a11y-results', {
body: JSON.stringify(results, null, 2),
contentType: 'application/json',
});

expect(results.violations).toEqual([]);
});
});
});

Targeted Scans​

Scan a Specific Region​

await test.step('scan only the table region', async () => {
const results = await new AxeBuilder({ page })
.include('.sapMTable') // CSS selector for the UI5 responsive table
.withTags(['wcag2a', 'wcag2aa'])
.analyze();

expect(results.violations).toEqual([]);
});

Exclude Known False Positives​

SAP UI5 renders some elements that axe may flag incorrectly (e.g., ARIA attributes on custom controls). Use disableRules to suppress known false positives:

const results = await new AxeBuilder({ page })
.disableRules([
'color-contrast', // UI5 theme handles contrast; may flag themed controls
'landmark-one-main', // FLP shell provides landmarks outside app scope
])
.analyze();

ARIA Roles in SAP UI5 Controls​

UI5 controls emit standard ARIA roles. Understanding these helps you write targeted scans and verify accessibility attributes.

UI5 ControlARIA RoleNotes
sap.m.ButtonbuttonIncludes aria-pressed for toggle buttons
sap.m.Inputtextboxaria-required, aria-invalid from control state
sap.m.Selectcomboboxaria-expanded toggled on open/close
sap.m.ComboBoxcomboboxaria-autocomplete="list"
sap.m.Tablegrid or tableDepends on responsive table mode
sap.m.ListlistboxItems have role="option"
sap.m.Dialogdialogaria-modal="true", aria-labelledby
sap.m.MessageStripalertAuto-announced by screen readers
sap.m.Panelregionaria-labelledby from header
sap.m.IconTabBartablistFilters have role="tab", content has role="tabpanel"
sap.m.SearchFieldsearchboxaria-label from placeholder
sap.m.CheckBoxcheckboxaria-checked state
sap.m.RadioButtonradioaria-checked, grouped via aria-labelledby

Testing ARIA Attributes with Praman​

test('verify input field accessibility attributes', async ({ ui5, page }) => {
await test.step('check required field ARIA', async () => {
const companyCode = await ui5.control({
controlType: 'sap.m.Input',
id: /companyCodeInput/,
});

// Get the DOM element to check ARIA attributes
const locator = await companyCode.getLocator();
await expect(locator).toHaveAttribute('aria-required', 'true');
await expect(locator).toHaveAttribute('role', 'textbox');
});

await test.step('check error state ARIA', async () => {
// Trigger validation by submitting empty required field
await ui5.click({ controlType: 'sap.m.Button', properties: { text: 'Save' } });

const errorInput = await ui5.control({
controlType: 'sap.m.Input',
properties: { valueState: 'Error' },
});

const locator = await errorInput.getLocator();
await expect(locator).toHaveAttribute('aria-invalid', 'true');
});
});

Keyboard Navigation Testing​

SAP UI5 supports keyboard navigation. Test common patterns:

test('keyboard navigation through form fields', async ({ ui5, page }) => {
await test.step('tab through form fields', async () => {
const firstInput = await ui5.control({
controlType: 'sap.m.Input',
id: /supplierInput/,
});
const locator = await firstInput.getLocator();

// Focus and tab
await locator.focus();
await page.keyboard.press('Tab');

// Verify focus moved to next field
const activeId = await page.evaluate(() => document.activeElement?.id);
expect(activeId).toContain('purchOrgInput');
});

await test.step('escape closes dialog', async () => {
// Open a value help dialog
await ui5.click({
controlType: 'sap.ui.core.Icon',
properties: { src: 'sap-icon://value-help' },
});

// Verify dialog is open
const dialog = await ui5.control({
controlType: 'sap.m.Dialog',
searchOpenDialogs: true,
});
expect(dialog).toBeTruthy();

// Press Escape to close
await page.keyboard.press('Escape');
});

await test.step('enter activates buttons', async () => {
const saveButton = await ui5.control({
controlType: 'sap.m.Button',
properties: { text: 'Save' },
});
const locator = await saveButton.getLocator();
await locator.focus();
await page.keyboard.press('Enter');
});
});

Continuous Accessibility Monitoring​

Per-Page Scan in CI​

Run axe on every page transition during E2E tests:

// tests/helpers/a11y-helper.ts
import AxeBuilder from '@axe-core/playwright';
import type { Page } from '@playwright/test';

export async function assertAccessible(page: Page, context?: string): Promise<void> {
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.disableRules(['color-contrast']) // Managed by UI5 theme
.analyze();

if (results.violations.length > 0) {
const summary = results.violations
.map((v) => `${v.id}: ${v.description} (${v.nodes.length} instances)`)
.join('\n');
throw new Error(`Accessibility violations${context ? ` on ${context}` : ''}:\n${summary}`);
}
}

Usage in tests:

import { assertAccessible } from './helpers/a11y-helper';

test('PO creation form is accessible', async ({ ui5Navigation, page }) => {
await ui5Navigation.navigateToIntent('#PurchaseOrder-create');
await assertAccessible(page, 'PO creation form');
});

Reporting​

Attach axe results to Playwright HTML report for post-test review:

await test.info().attach('axe-violations', {
body: JSON.stringify(results.violations, null, 2),
contentType: 'application/json',
});

Common UI5 Accessibility Issues​

IssueAffected ControlsFix
Missing aria-label on icon-only buttonssap.m.Button with icon onlySet tooltip property in app code
Duplicate IDsAuto-generated fragment IDsUse viewId scoping in selectors
Missing form labelssap.m.Input without sap.m.LabelEnsure labels are associated via labelFor
Low contrast in custom themesCustom CSS overridesTest with default sap_horizon theme
Missing heading hierarchysap.m.Title without semantic levelSet level property (H1-H6)