ENG-1748: Test 1321 False Negative on A Elements with role=link

Bug (ENG-1748): Access Engine test 1321 (label in name — the visible text of a link must appear in its accessible name, WCAG 2.5.3) scoped its candidate set to a[data-ae_vis][data-ae_avat][data-ae_ar="null"][data-ae_an] — anchors with no ARIA role at all. Any role attribute took the anchor out of scope, including role="link", which does not change what the element is. Markup such as <a href="foo" role="link" aria-label="not right">Testing this out</a> was silently skipped — a false negative.

Impact: Speech-input users cannot activate a link by speaking its visible text when the accessible name does not contain it. Design systems and component libraries frequently emit a redundant role="link" on anchors, so every label-in-name failure inside those components went unreported.

Test Logic (test 1321):

Common Scenarios:

Fix: Extend the candidate set with the computed-role attribute added in ENG-1730 (data-ae_computed_role, the explicit-or-implicit ARIA role):

a[data-ae_vis][data-ae_avat][data-ae_ar="null"][data-ae_an], a[data-ae_vis][data-ae_avat][data-ae_computed_role="link"][data-ae_an]

The computed-role branch matches role="link" in any letter case; the original data-ae_ar="null" branch is retained so role-less anchors stay in scope exactly as before — the change only adds coverage, it never removes it. Both branches still require data-ae_an: accessible names are calculated for role-less elements and for [data-ae_ar='link'], so an anchor that only resolves to link through an invalid first role token (role="foo link") has no name to compare against and stays out of scope.
N/A — out of scope
PASS — not flagged (correct)
FAIL — flagged (correct; the role=link cases are the ENG-1748 repro, the rest are regression guards)

N/A Tests — Test 1321 Does Not Apply

Anchor with role="button" and a mismatched aria-label other role

stay

The anchor is exposed as a button, not a link, so test 1321 does not apply. Only role=link (or no role) is in scope. Should not be flagged.

Anchor with role="presentation" and a mismatched aria-label other role

stay

An explicit presentational role removes the link semantics the test is about. Out of scope, unchanged by ENG-1748. Should not be flagged.

Anchor with role="link" hidden with display:none hidden

(Anchor is present but hidden via display:none.)

Test 1321 only evaluates elements that are not intentionally hidden: the selector requires [data-ae_vis]. Should not be flagged.

Anchor with role="link" and aria-hidden="true" not exposed

Not available to assistive technologies, so the selector's [data-ae_avat] requirement excludes it. Should not be flagged.

Icon-only anchor with role="link" — no visible text no text

With no visible text there is nothing that must appear in the accessible name; the link's own pseudo content is suppressed when an aria-label supplies the name (ENG-1722). Should not be flagged.

Known gap: role="foo link" with a mismatched aria-label token list

stay

Browsers skip the unknown token and treat this as a link, and the computed role is link — but accessible names are only calculated for role-less elements and for [data-ae_ar='link'], and data-ae_ar here is foo. With no data-ae_an there is nothing to compare, so the element stays out of scope. Documented gap, not flagged. (Tests 37 and 412 flag this element separately for the invalid role token.)

PASS Tests — In Scope, No Violation

Baseline: role-less anchor whose aria-label contains the visible text no role

stay here

Visible text is a substring of the accessible name. Should not be flagged.

Anchor with role="link" whose aria-label contains the visible text role=link

stay here

Newly in scope after ENG-1748, and compliant: the accessible name starts with the visible text. Should not be flagged.

Anchor with role="link" named by its own text role=link

Annual report

No aria-label, so the accessible name is the visible text. Should not be flagged.

Anchor with role="link" matching only after punctuation normalisation punctuation

state-of-the-art

The strict substring check fails on the hyphens, but the punctuation-insensitive fallback matches. Should not be flagged.

FAIL Tests — Correctly Flagged

ENG-1748 repro cases — missed before the fix

Ticket repro: role="link" anchor with a mismatched aria-label role=link

Testing this out

The markup from ENG-1748: the accessible name "not right" does not contain the visible text. Pre-fix: not flagged ❌. Post-fix: flagged ✓.

Uppercase role="LINK" anchor with a mismatched aria-label case

stay

ARIA role tokens are case-insensitive and the computed role is normalised to lower case. Pre-fix: not flagged ❌. Post-fix: flagged ✓.

role="link" anchor with no href and a mismatched aria-label no href

stay

Without href the anchor has no implicit link role, but the explicit role="link" makes it one — and a speech-input user still says "stay". Pre-fix: not flagged ❌. Post-fix: flagged ✓. (Test 1119 also flags this element for having neither href nor a tabindex — expected and unrelated to test 1321.)

role="link" anchor named by aria-labelledby pointing at other text labelledby

Label source: Download the 2026 media kit

Press pack

The accessible name comes from another element and does not contain the visible "Press pack". Pre-fix: not flagged ❌. Post-fix: flagged ✓.

Regression guards — flagged before and after the fix

Role-less anchor with a mismatched aria-label no role

stay

The original test 1321 case. Must stay flagged ✓.

Role-less anchor with no href and a mismatched aria-label no role, no href

stay

Its computed role is empty (no href), so it is only in scope through the retained data-ae_ar="null" branch. This case guards against narrowing the selector to the computed role alone. Must stay flagged ✓. (Test 1119 also flags it for having neither href nor a tabindex — expected and unrelated to test 1321.)

Real-World Scenarios

Design-system "Read more" component emitting a redundant role="link" role=link

Read more

A speech-input user says "Read more" but the accessible name never contains it. Pre-fix: not flagged ❌. Post-fix: flagged ✓.

Card link with role="link" whose label drops the visible heading text role=link

Antelope print, £24.00

The shortened label omits the visible product name. Pre-fix: not flagged ❌. Post-fix: flagged ✓.