Skip to content

harmonia template: axe-core checks the generated pages against WCAG 2.1 AA, red on serious/critical (#7645) - #7686

Merged
delchev merged 1 commit into
eclipse-dirigible:masterfrom
NicoleNG18:issue-7645-axe-accessibility
Oct 5, 2026
Merged

delchev merged 1 commit into
eclipse-dirigible:masterfrom
NicoleNG18:issue-7645-axe-accessibility

Conversation

@NicoleNG18

Copy link
Copy Markdown
Contributor

Cause

The Harmonia templates carry ARIA where the component library put it, but nothing checked it: there was no axe-core, pa11y or Lighthouse anywhere in tests/ or .github/. So every page the generator emits was one template edit away from an accessibility regression no one would see.

Change

The check

AccessibilityHarmoniaIT (Selenide, ui tag, so the nightly and master ui shards). It generates and publishes an intent application, AccessibilityHarmoniaIT/app.intent, with a record behind every page kind: a customer, an invoice with a line and a status, and a review task in the Inbox. It then scans with axe-core against the WCAG 2.1 A/AA rule tags (wcag2a, wcag2aa, wcag21a, wcag21aa):

  • the manage list;
  • the manage form;
  • the document page;
  • its read-only preview (the print preview);
  • the Inbox.

Every page is scanned in both colour schemes. The scheme is pinned, because Harmonia's default follows the OS and would differ between a developer's machine and CI, and the scan waits for the switch's colour transitions to finish.

  • Report: every page's full axe JSON, plus summary.txt with the counts per impact, goes to target/failsafe-reports/axe/. The IT jobs already upload that folder on every outcome.
  • Gate: a serious or critical violation fails the test, naming the page, the rule and the elements. minor/moderate findings are reported only.
  • Checks axe can't make: the status step indicator exposes aria-current="step", and the line dialog returns focus to the button that opened it.

It uses Deque's Selenium binding (com.deque.html.axe-core:selenium), which runs the same axe-core engine and rules as @axe-core/playwright, on the Chrome the Selenide ITs already drive. The Playwright Java binding would have added a 194 MB driver bundle and a second browser stack to tests-integrations. The binding's own old selenium-java is excluded, because Selenide supplies Selenium.

HarmoniaAccessibilityMarkupIT (static, untagged, so it's in the PR smoke gate) guards rules on markup no intent application renders: the perspective list view exists only for hand-authored models. A sortable header must sort through a button, and every step-indicator trigger must bind aria-current.

The Playwright app-test runner (npm/test, published as @aerokit/sdk/test) gets the same check as an a11y flow using @axe-core/playwright (an optional peer dependency in both package manifests). It scans each entity's list page and create form, attaches the JSON to the Playwright report, and is report-only by default; APPTEST_A11Y=strict makes it fail, and off skips it. This is the "report first, then red" rollout for downstream apps.

Template fixes the first report and the issue's list surfaced

Finding Fix
critical aria-required-attr: the column-resize grip (a focusable role="separator") set aria-valuenow only once focused app.js announces it when the grip is created; focus and every resize keep it current
serious color-contrast: the document's "your step" strip put text-muted-foreground on its primary tint (4.32:1) text-foreground
the perspective list sorted on a click on the <th>: not focusable, no sort state a real <button type="button"> inside the header, :aria-sort on the cell (the manage list's shape)
Harmonia's step indicator marks the active step visually only both indicators (document page, BPM task form) bind :aria-current="... ? 'step' : null"

Reviewed, no change needed:

  • Labels: every input carries a bound label; axe's label rule finds nothing.
  • Dialog focus: Harmonia's dialog traps focus and returns it on close itself, per its 3.1.2 reference and confirmed by the IT.
  • role="presentation": all 194 uses (the issue counted 431) are on decorative <svg> icons, none on table markup.

Not enforced, and why

Contrast against Harmonia 3.1.2's own --primary. White on it is 3.67:1 in light mode and 4.43:1 in dark mode, and it as dark-mode link text is 4.26:1. In dark mode no single token can pass both uses, so this is fixed in codbex/harmonia, not per template. The exemption is per node: a contrast failure counts unless the primary colour (resolved in the page) is its foreground or background, so every other contrast failure still fails the test. The violations stay in the report. Overriding the palette in application-core/shell/css/app.css is the alternative, but it would recolour every platform shell; I'll do it if reviewers prefer that.

Verified

  • AccessibilityHarmoniaIT green: 5 pages × 2 schemes, about 27 s. HarmoniaAccessibilityMarkupIT green (2 tests).
  • Red on a label binding removed, as the issue asks. A temporary edit dropping the form labels' for= failed with form-light: [critical] label - Form elements must have labels (3 elements: [#f_Name], [#f_Email], [#f_Vip]), the same for form-dark. The template was restored byte-identical.
  • Related ITs green: HarmoniaContractIT (the changed markup passes the directive and class allowlist), HarmoniaListColumnsIT, HarmoniaHierarchyTreeTableIT, I18nKeyCoverageIT, HarmoniaTaskFormLayoutIT, ShellRuntimeVintageIT, and IntentEmissionCoverageIT (135 s, compiles and runs the generated output).
  • Formatting: mvn -T 1C formatter:validate with the cache wiped, BUILD SUCCESS.

Not verified / not in this PR

  • The npm a11y flow was syntax-checked (node --check) only, not run against an instance.
  • The WCAG statement template for the help pages (the issue's item 3) belongs in the separate dirigible-io.github.io repository. A follow-up PR there.
  • Escape doesn't close the document's line dialog. Harmonia leaves dismissal to the page. This is good dialog practice but not in the issue's list, so it's left out of this PR.
  • Coverage: only the default intent pages are scanned. The my/partner/admin shells and the report, calendar and slots views are not yet.

Fixes #7645

🤖 Generated with Claude Code

….1 AA, red on serious/critical (eclipse-dirigible#7645)

The Harmonia templates carry ARIA where the component library put it, but
nothing checked it: no axe-core, pa11y or Lighthouse anywhere, so every
generated page was one template edit away from a regression no one saw.

AccessibilityHarmoniaIT generates and publishes an intent application and
scans its manage list, manage form, document page, read-only preview and
Inbox with axe-core (Deque's Selenium binding, on the Chrome the Selenide
ITs already drive), WCAG 2.1 A/AA rules, in both colour schemes. Every
page's result and a count per impact go to target/failsafe-reports/axe/,
which the IT jobs upload; a serious or critical violation fails it. It
also asserts what axe cannot see: the status step indicator announces the
active step, and the line dialog returns focus to the button that opened
it. HarmoniaAccessibilityMarkupIT guards the rules on markup no intent
application renders: a sortable header sorts through a button, every step
trigger binds aria-current.

Template fixes the first report and the issue's list surfaced:
- the column-resize grip (a focusable role=separator) announced
  aria-valuenow only once focused - required from the start (critical);
- the document's "your step" strip put muted text on its primary tint
  (4.32:1) - now foreground;
- the perspective list sorted on a click on the <th> - a real button now,
  with aria-sort;
- both status step indicators (document page, BPM task form) bind
  aria-current="step"; Harmonia marks the active step visually only.
Reviewed, no change: inputs carry bound labels (the label rule finds
nothing), Harmonia's dialog traps and returns focus itself, and the 194
role="presentation" are all on decorative svg icons, none on table markup.

Not enforced: contrast against Harmonia 3.1.2's own --primary (white on
it 3.67:1 light / 4.43:1 dark, it as dark-mode link text 4.26:1 - one
token cannot pass both) - a node-level exemption, so every other contrast
failure still fails the test; it is fixed in codbex/harmonia.

The Playwright app-test runner (npm/test, published in @aerokit/sdk) gets
the same check as an a11y flow with @axe-core/playwright (optional peer):
report-only by default, APPTEST_A11Y=strict to fail.

Verified: AccessibilityHarmoniaIT and HarmoniaAccessibilityMarkupIT green,
red with the form labels' for= binding removed ([critical] label on
#f_Name, #f_Email, #f_Vip); HarmoniaContractIT, HarmoniaListColumnsIT,
HarmoniaHierarchyTreeTableIT, I18nKeyCoverageIT, HarmoniaTaskFormLayoutIT,
ShellRuntimeVintageIT, IntentEmissionCoverageIT green; formatter:validate
with the cache wiped. The npm a11y flow was syntax-checked, not run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread npm/test/src/index.js
myFlow(manifest, entity, opts);
multilingualFlow(manifest, entity, opts);
shellFlow(manifest, entity, opts);
a11yFlow(manifest, entity, opts);
return false;
}
for (int channel = 1; channel < 7; channel += 2) {
int a = Integer.parseInt(hex.substring(channel, channel + 2), 16);
}
for (int channel = 1; channel < 7; channel += 2) {
int a = Integer.parseInt(hex.substring(channel, channel + 2), 16);
int b = Integer.parseInt(expected.substring(channel, channel + 2), 16);
@delchev
delchev merged commit 624d89a into eclipse-dirigible:master Oct 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants