Skip to content

NcSelect option accessible names are mangled — getByRole('option',{name}) cannot match any label of 10+ characters (fleet-wide) #350

Description

@rubenvdlinde

Summary

getByRole('option', { name: … }) cannot match any NcSelect option whose label is 10 characters or longer, in any app in the fleet. The option's accessible name has a space injected into the middle of it.

Measured on openbuild (NC 34, @nextcloud/vue 9.9.0) while fixing tests/e2e/automations-rbac.spec.ts. The application is called "RBAC Automations App". Playwright's page snapshot:

- option "RBAC Autom ations App"      # <- space inside "Automations"
  - generic "RBAC Automations App"    # correct, but only via title=
    - generic: RBAC Autom
    - generic: ations App

Cause

NcEllipsisedOption implements middle-ellipsis by splitting the label into two <span>s:

needsTruncate() { return this.name && this.name.length >= 10 }
split()         { return this.name.length - Math.min(Math.floor(this.name.length / 2), 10) }
part1 = name.slice(0, split)
part2 = name.slice(split)

The wrapper <span class="name-parts" title="{full name}"> carries the correct name, so the inner node's accessible name is right. But the option role computes its name from contents, and the accessible-name algorithm inserts a space at each element boundary — so the option announces the mangled form.

Every label of 10+ characters is affected, at index len - min(floor(len/2), 10):

label len announced as
RBAC Automations App 20 RBAC Autom ations App
PW Version Lifecycle 20 PW Version Lifecycle (splits at a real space, so invisible)
production 10 produ ction
development 11 develo pment

Note the third row: short, ordinary values like production are affected too, which is easy to miss.

Why this is worth a fleet-level issue

Accessibility. A screen reader reads the mangled name. WCAG 2.2 AA 4.1.2 (Name, Role, Value); arguably 2.5.3 (Label in Name) too, since the accessible name no longer contains the visible label as a contiguous string.

It has already produced two wrong issues. In openbuild the locator timed out, the timeout was read as "the fixture is missing / the editor cannot see the application", and that inference became ConductionNL/openbuild#171 and the second comment on ConductionNL/openbuild#173 — the latter an architecture issue. Both are now retracted; the API was measured directly and works. A locator that finds nothing says nothing about the API underneath it, and this is a locator that finds nothing for a reason that has no connection to the app at all.

Six other agents are writing Playwright role queries against NcSelect right now.

The honest workaround

Target the component's own record of the full name:

function selectOption(page: Page, label: string) {
  return page.getByRole('option').filter({ has: page.locator(`[title="${label}"]`) })
}

Do not relax the regex to tolerate arbitrary whitespace: that hides a real a11y defect and will match the wrong option in a longer list.

Where the fix belongs

NcEllipsisedOption is upstream @nextcloud/vue, not @conduction/nextcloud-vue, so we cannot fix it by cutting one of our own releases. The upstream fix is small — give the split wrapper an explicit aria-label (or role="text"/aria-hidden on the parts) so the accessible name is computed from the whole name rather than from the two fragments. This issue tracks (a) the fleet-wide test-authoring guidance above and (b) raising it upstream.

Confirmed by: Playwright page snapshot on a live instance, plus reading node_modules/@nextcloud/vue/dist/chunks/NcEllipsisedOption-*.mjs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions