Skip to main content

@atomic-testing/component-driver-fluent-v9

Component drivers for Fluent UI v9 ("Fluent 2", @fluentui/react-components). Component drivers expose simple APIs for unit tests or end-to-end tests to interact with Fluent-based componentsβ€”reading state and driving actionsβ€”so test engineers focus on test flows instead of the component internals.

The problem​

Fluent v9 styles every component with Griffel, an atomic CSS-in-JS engine β€” the classes it emits are hashed and change across builds, so they are not stable test anchors. Fluent also ships a strong accessibility program (it is the Microsoft 365/Office design system), so the stable anchors, in priority order, are:

  1. role + ARIA state β€” Fluent renders correct roles/aria-* per component (e.g. aria-pressed on ToggleButton, aria-disabled on Link).
  2. Fluent's own un-hashed structural classes β€” every component stamps a plain fui-<ComponentName> class (and fui-<ComponentName>__<part> for sub-parts, e.g. fui-Field__hint) alongside the hashed Griffel utility classes. These are Fluent-owned and stable across releases; the drivers in this package use them where role/ARIA isn't enough (e.g. FieldDriver's hint/validation-message reads).
  3. Never the hashed Griffel utility classes.

Several core controls (Input, Textarea, Checkbox, Switch, Radio, Select) render as real native form elements at their root β€” data-testid (or any locator) placed on the component lands directly on the native <input>/<textarea>/<select>, not a styled wrapper β€” so this package reuses @atomic-testing/component-driver-html's drivers wholesale wherever that holds.

The solution​

The drivers in this package locate Fluent parts by those stable anchors and expose high-level interactions. Combined with the React adapter (@atomic-testing/react-19 or another React major), the same scene definitions run across DOM (jsdom) and end-to-end (Playwright) tests.

Target package & version pin​

This driver targets Fluent UI v9 and is declared as a peer dependency pinned to ^9.0.0: consumers bring their own @fluentui/react-components. Fluent v8 (@fluentui/react) is a materially different DOM/styling contract (mergeStyles, no Griffel) and is out of scope for this package.

Installation​

npm install @atomic-testing/core @atomic-testing/react-19 \
@atomic-testing/component-driver-html @atomic-testing/component-driver-fluent-v9 \
@fluentui/react-components --save-dev

Refer to the documentation for usage patterns and examples.

Portal & overlay recipe (Wave 2)​

Dialog, Popover, Drawer (OverlayDrawer), Menu, Toast/Toaster, and TeachingPopover all portal by default β€” verified against rendered DOM (@fluentui/react-components@9.74.3): each mounts into a cloned FluentProvider on document.body (Fluent's own mountNode default, confirmed against @fluentui/react-portal's types), a sibling of the render root rather than a descendant of the trigger. InlineDrawer is the one exception β€” it renders in-tree, no portal at all. Two distinct re-root techniques cover this wave, chosen per component by whether the scene's own locator can land directly on the portalled surface:

  • Static re-root + class compounding (Dialog, Popover, OverlayDrawer, TeachingPopover, Toaster) β€” the driver overrides the overriddenParentLocator()/overrideLocatorRelativePosition() static hooks (see packages/core/src/drivers/ComponentDriver.ts) to re-root at the un-hashed Fluent structural class of that surface (e.g. .fui-DialogSurface, .fui-PopoverSurface), and the scene's own locator (forwarded onto the surface component, e.g. <DialogSurface data-testid="...">) compounds onto that SAME element. Two simultaneously open instances disambiguate correctly because each surface only matches its own forwarded test id β€” verified with two open dialogs/popovers side by side. Anchored on the class rather than role where the role is shared: role="dialog" alone is worn by Dialog, OverlayDrawer, and TeachingPopover; role="group" is far too generic for Popover alone.
  • Trigger-anchored + byLinkedElement (Menu) β€” the driver is constructed from the TRIGGER locator and resolves the portalled MenuList by following the trigger's id to the list's aria-labelledby, re-read fresh on every call (byLinkedElement, the same technique component-driver-radix-v1 uses for its own aria-controls/aria-describedby links). Necessary because role="menu"/role="presentation" are identical across every simultaneously open menu β€” a static class/role re-root cannot tell "this menu" from a sibling one, but the trigger↔list id link can. Verified with two open menus side by side.
  • Trigger-anchored + byLinkedElement on aria-controls (Combobox, Dropdown, TagPicker, Wave 3) β€” the same byLinkedElement idiom as Menu, but following the trigger/input's aria-controls (not aria-labelledby) to the portalled listbox, and the attribute is present ONLY while open (absent entirely, not merely empty, once closed) β€” every listbox-reading method guards the resolution in try/catch, and isOpen() reads the trigger's own aria-expanded rather than listbox existence, since Fluent keeps the listbox mounted briefly after a legitimate close.

Tooltip is trigger-anchored too, but for a different reason: its content carries no reliable per-instance link in the default relationship="label" mode (only relationship="description" sets aria-describedby), so isOpen() falls back to a best-effort shared-portal check in that mode β€” see the Known gaps section below and TooltipDriver's TSDoc.

Escape dismisses the topmost stacked overlay, not a specific targeted one β€” verified against real Chromium: with two overlays open, Escape always closes the most-recently-opened one, regardless of which overlay's locator the key event is dispatched on (Fluent's dismiss handling is a global, stack-ordered listener). Drive closeByEscape() on the LAST-opened instance in a stacked scenario.

Drivers​

Drivers land in waves (see the umbrella issue #1098); all 6 waves (core form primitives; overlays & portals; selection & specialized inputs; navigation & disclosure; data display & feedback; complex/composite) ship in full below, plus a Wave 6 follow-up (issue #1138) adding FlatTree/FlatTreeItem β€” this is the complete driver catalog.

DriverFluent componentNotes
ButtonDriverButtonNative <button>; delegates wholesale to HTMLButtonDriver.
CompoundButtonDriverCompoundButtonSame native <button> root as Button; getSecondaryContent() reads the fui-CompoundButton__secondaryContent part (see JSDoc for the known primary/secondary text-splitting limitation).
ToggleButtonDriverToggleButtonNative <button>; pressed state read/written via aria-pressed (no native "pressed" concept exists for <button>).
InputDriverInputThe root IS a native <input> β€” full HTMLTextInputDriver surface, incl. isError via aria-invalid.
TextareaDriverTextareaThe root IS a native <textarea> β€” full HTMLTextAreaDriver surface.
CheckboxDriverCheckboxExtends HTMLCheckboxDriver (the root IS a real native <input type="checkbox">); label prop renders a sibling <label for>, resolved via the for↔id link. isIndeterminate() reads the live .indeterminate property via the :indeterminate CSS pseudo-class.
SwitchDriverSwitchSame shape as Checkbox, but no value concept (pure on/off) β€” does not implement IFormFieldDriver.
RadioDriverRadioThe root IS a real native <input type="radio">; setSelected(false) is rejected (native radio semantics).
RadioGroupDriverRadioGroupDelegates to HTMLRadioButtonGroupDriver β€” point its ScenePart locator at the radio inputs (e.g. the group container appended with an input[type="radio"] descendant selector), not at the [role="radiogroup"] wrapper.
SelectDriverSelectThe root IS a native <select> β€” full HTMLSelectDriver surface.
LabelDriverLabelPlain native <label>; getFor() reads the linked control's id.
FieldDriverFieldContainer wrapper; getLabel/getHint/getValidationMessage read descendant parts anchored on Fluent's fui-Field__* structural classes.
LinkDriverLinkNative <a>; overrides isDisabled to read aria-disabled (an anchor has no native disabled property).
DividerDriverDivider[role="separator"]; getOrientation() reads aria-orientation.
ImageDriverImageNative <img>; getSrc/getAlt read attributes directly.
TextDriverTextPlain content wrapper; all behavior is inherited (getText).

Wave 2 β€” overlays & portals (see the portal & overlay recipe above):

DriverFluent componentNotes
DialogDriverDialog (+ DialogSurface/Body/Title/Content/Actions)Portalled; re-roots on .fui-DialogSurface. isModal() reads aria-modal; non-modal dialogs auto-render a close X. No portable closeByBackdropClick (the backdrop is an un-linkable document.body sibling, not a descendant) β€” only closeByEscape.
PopoverDriverPopover (+ PopoverTrigger/Surface)Portalled; re-roots on .fui-PopoverSurface (role is the too-generic "group"). Shares that class with TeachingPopoverSurface β€” use TeachingPopoverDriver for that component specifically.
OverlayDrawerDriverOverlayDrawerPortalled; re-roots on .fui-OverlayDrawer (role="dialog", shared with Dialog/TeachingPopover). defaultOpen is deprecated/non-functional β€” drive it via the controlled open prop.
InlineDrawerDriverInlineDrawerRenders in-tree β€” no portal, no re-root, unlike every other driver in this wave.
DrawerDriverBaseshared basegetHeaderTitle/getBodyText + open/close lifecycle common to both drawer variants.
MenuDriverMenu (+ MenuTrigger/Popover/List)Constructed from the TRIGGER locator; resolves the portalled MenuList via the trigger id ↔ list aria-labelledby link (byLinkedElement) β€” correctly disambiguates two simultaneously open menus.
MenuItemDriverMenuItemrole="menuitem"; getLabel/isDisabled.
MenuItemCheckboxDriverMenuItemCheckboxrole="menuitemcheckbox"; adds isChecked() via aria-checked. Selecting one persists the open menu.
MenuItemRadioDriverMenuItemRadiorole="menuitemradio"; adds isChecked() via aria-checked. Selecting one closes the menu (unlike checkbox items) β€” re-open to observe the persisted choice.
MenuButtonDriverMenuButtonNative <button> (delegates to HTMLButtonDriver); getMenu() returns the MenuDriver it opens.
SplitButtonDriverSplitButtonWrapper <div> around a primary action button and a menu-invoking button; clickPrimary()/getMenu() expose each half separately (the base click() on the wrapper is not meaningful).
TooltipDriverTooltipTrigger-anchored. getContent() reads aria-label (default relationship="label") or follows aria-describedby (relationship="description") β€” available regardless of open state, since Fluent mounts the content unconditionally. isOpen()'s fallback for "label"-relationship tooltips is best-effort when multiple tooltips are mounted (see Known gaps). Opens via .focus(), not hover (hover does not reveal Fluent's tooltip under jsdom).
ToasterDriverToasterPortalled; re-roots on .fui-Toaster. Deliberately has no caller-supplied interior β€” toasts arrive dynamically via dispatchToast, read positionally/by-title (getToastByIndex/getToastByTitle) rather than as a declared scene.
ToastDriverToast (+ Title/Body)getTitle/getBodyText; no built-in dismiss button (use a declared content part for consumer-supplied actions, like component-driver-mui-v9's SnackbarDriver).
TeachingPopoverDriverTeachingPopover (+ Header/Title/Body/Footer)Portalled; re-roots on the more-specific .fui-TeachingPopoverSurface (not the shared .fui-PopoverSurface). Has a built-in dismiss button, unlike plain Dialog/Popover.

Wave 3 β€” selection & specialized inputs:

DriverFluent componentNotes
ComboboxDriverCombobox (+ Option/OptionGroup)Root IS a native <input role="combobox"> β€” extends HTMLTextInputDriver wholesale. Listbox is trigger-anchored via aria-controls byLinkedElement (same technique as MenuDriver). No selectByValue β€” Fluent never reflects Option's value to the DOM, only the label. Single-select only.
ComboboxOptionDriverOptionrole="option"; getLabel/isDisabled/isSelected β€” a standalone class, not sharing MenuItemDriver's inheritance.
DropdownDriverDropdown (@fluentui/react-select)NOT the native-<select>-backed Select despite the shared naming β€” a fully custom combobox widget. Trigger is a real <button role="combobox">; isOpen() reads its aria-expanded (the listbox stays mounted post-close). Listbox resolved via aria-controls byLinkedElement. Single-select only.
DropdownOptionDriverOption (single-select Dropdown)role="option"; getLabel/isSelected/isDisabled.
SliderDriverSliderRoot IS a native <input type="range"> β€” extends HTMLRangeInputDriver wholesale. Adds getMin/getMax/getStep/isRequired/getLabel. Single-thumb only β€” Fluent v9 ships no multi-thumb variant.
SpinButtonDriverSpinButtonRoot IS the native <input role="spinbutton">; stepper <button>s reached via the general-sibling combinator (~), same escape hatch as component-driver-astryx's NumberInputDriver. setValue types then blurs to commit; increment/decrement click the steppers; moveToMin/moveToMax/incrementByPage/decrementByPage drive Home/End/PageUp/PageDown.
SwatchPickerDriverSwatchPickerDoes not portal. Items matched on the un-hashed .fui-ColorSwatch class (role flips radio/gridcell with layout). No getValue/selectByValue β€” color-based equivalents instead (getSwatchColors/selectByColor/getSelectedColor).
SwatchPickerItemDriverColorSwatchReal native <button>; isSelected() reads aria-checked falling back to aria-selected. setSelected(false) rejected (no self-deselect). getColor() reads the --fui-SwatchPicker--color inline CSS var β€” no getValue().
RatingDriverRatingRoot IS role="radiogroup" with visually-hidden native radio items; getValue/setValue drive the :checked radio via Interactor.activate. No native disabled/readOnly β€” isDisabled() reads a :disabled descendant radio (consumer <fieldset disabled> cascade).
RatingDisplayDriverRatingDisplayRead-only sibling of Rating; root is role="img", never radiogroup. Does not implement IInputDriver. getValue/getCount read fui-RatingDisplay__valueText/__countText.
TagDriverTagStatic, non-dismissible tag; root IS <span class="fui-Tag">. getLabel() reads the fui-Tag__primaryText part. Does not implement IDisableableDriver β€” disabled has zero DOM reflection.
InteractionTagDriverInteractionTag (+ Primary/Secondary)Dismissible tag; wrapper <div> around two real native <button>s, delegated wholesale to HTMLButtonDriver. isDisabled() reads the Primary button's disabled. clickPrimary() selects; dismiss() removes.
TagGroupDriverTagGroupList container; never portals. isDisabled() reads the group's own always-present aria-disabled. getTagCount/getTagLabels/getTagByIndex enumerate mixed Tag/InteractionTag children positionally.
TagPickerDriverTagPicker (+ Control/Group/Input/List/Option)Constructed from TagPickerControl's locator (<TagPicker> itself renders no DOM). Portalled TagPickerList resolved via aria-controls byLinkedElement; isOpen reads the input's aria-expanded. getSelectedLabels/removeSelected read the in-tree TagPickerGroup; getOptionCount/selectByLabel auto-open the portalled list.
TagPickerOptionDriverTagPickerOption / a selected TagShared getLabel/isDisabled surface for both an open-list option and an already-selected tag (both render role="option"). No portable isSelected for either.
SearchBoxDriverSearchBoxSame native <input type="search"> root as Input β€” full HTMLTextInputDriver surface, plus clear()/hasClearButton(). The dismiss button renders unconditionally by default and is a sibling with no per-instance link, resolved via the ancestor-:has() re-root (same idiom as component-driver-mui-v9's CheckboxDriver).
ColorPickerDriverColorPicker (+ ColorArea/ColorSlider)Composite; renders its own <div class="fui-ColorPicker"> root, unlike a bare context provider. area/hueSlider parts locate children by un-hashed structural classes. Strictly a controlled component (color/onColorChange only).
ColorAreaDriverColorAreaThe 2-D saturation/value picker; root wraps two real native <input type="range">s (inputX/inputY), driven via setRangeValue/getInputValue. No disabled/required (not valid attributes on a plain <div>).
ColorSliderDriverColorSliderThe 1-D hue slider; locator lands straight on a real native <input type="range"> β€” same shape as the standalone SliderDriver, so this driver extends HTMLRangeInputDriver wholesale.

Wave 4 β€” navigation & disclosure: none of this wave's components portal by default except NavDrawer (which reuses the Wave 2 OverlayDrawer recipe). A unifying design rule shapes the whole wave: a driver models each independently INTERACTIVE/addressable unit; purely structural wrapper elements (a decorative divider, a header/panel pairing that's always 1:1 with its item, a non-interactive group <div>) fold into their interactive sibling's driver rather than getting their own class β€” the same way AccordionHeader/AccordionPanel fold into AccordionItemDriver, and BreadcrumbDivider isn't modeled at all (see its row below).

DriverFluent componentNotes
TabListDriverTabListA ListComponentDriver over role="tab" children (templated off component-driver-mui-v9's TabsDriver), since TabList has no built-in TabPanel β€” panel wiring is left to the consumer. Tab's value prop genuinely reflects onto the native value attribute (unlike Option.value elsewhere in this package), so getSelectedValue/selectByValue are reliable, un-hashed reads.
TabDriverTabReal <button role="tab">; aria-selected is absent entirely (not "false") when disabled. Overrides getText(): when reserveSelectedTabSpace (the default) and the tab is unselected, Fluent renders an invisible SECOND copy of the label in a fui-Tab__content--reserved-space span purely to reserve layout width β€” the inherited whole-button text read would return the label doubled (verified: "ProfileProfile"), so this driver reads .fui-Tab__content specifically instead.
BreadcrumbDriverBreadcrumbItems live inside a child <ol role="list">, interleaved with decorative <li> dividers β€” both share the <li> tag, so item enumeration uses childListHelper's :nth-child + class-selector filter (same mixed-sibling shape TagGroupDriver handles) rather than ListComponentDriver. BreadcrumbDivider has no driver β€” purely decorative (aria-hidden), see the wave-level rule above.
BreadcrumbItemDriverBreadcrumbItemThe <li> wrapper itself carries no interactive state; folds in BreadcrumbButton's behavior via getButton() and overrides click() to target the button, not the wrapper.
BreadcrumbButtonDriverBreadcrumbButtonRenders <a> (with href) or <button> β€” isDisabled() combines a native-disabled check with an aria-disabled read (mirrors LinkDriver's reasoning) since either element shape is possible. isCurrent() reads aria-current="page", present only when the current prop is set.
AccordionDriverAccordionA ListComponentDriver over fui-AccordionItem children (homogeneous <div> siblings, safe for :nth-of-type). multiple/collapsible mode has zero DOM reflection (grepped the compiled package) β€” this driver exposes no mode getter; drive/observe expansion per item instead.
AccordionItemDriverAccordionItem (+ AccordionHeader/AccordionPanel)Folds header/panel state in directly (templated off component-driver-radix-v1's single-item AccordionDriver). AccordionPanel fully UNMOUNTS while collapsed (Fluent's hard-coded unmountOnExit) β€” getPanelText() returns null whenever absent. collapse() no-ops on the parent's OWN collapsible prop (Fluent refuses to reach zero open items otherwise) β€” see Known Gaps. click() no-ops on a disabled header, same portability contract as RadioDriver.setSelected.
NavDriverNavNever portals. Item enumeration (NavItem/NavCategory/NavCategoryItem/NavSubItem, arbitrarily nested) walks via childListHelper's groupSelector: '*' recursion, flattening the tree. Shares its item-query surface with NavDrawerDriver via the internal NavDriverBase.
NavDrawerDriverNavDrawerPortal-backed by default (wraps Drawer β†’ OverlayDrawer, same as Wave 2) β€” re-roots on the un-hashed fui-NavDrawer class. Corrects the umbrella issue's hypothesis: "Nav's flyouts are overlay-backed" is true only of NavDrawer's own outer surface, NOT of NavCategory expansion inside it (see NavCategoryItemDriver). Targets the default (portal) variant only β€” type="inline" isn't covered.
NavItemDriverNavItem / NavSubItemRenders <a href> or <button> β€” the component's OWN root IS the interactive element (no wrapper to look past, unlike BreadcrumbItemDriver). aria-current is always a literal "page"/"false" string, never merely absent.
NavCategoryItemDriverNavCategoryItemExtends NavItemDriver, adding expand/collapse. Always a real <button>, never <a>. No portal β€” grepped the entire compiled @fluentui/react-nav package for Popover/Menu/role="menu": zero matches; the sub-item group is a same-tree animated accordion, reached via the general-sibling CSS combinator (same escape hatch SpinButtonDriver uses for its steppers).
ToolbarDriverToolbararia-orientation is present only when vertical β€” getOrientation() defaults the horizontal case rather than passing through null. Button enumeration (getButtonByLabel) descends one level into .fui-ToolbarGroup wrappers via childListHelper's groupSelector (ToolbarRadioGroup shares this same class, no separate fui-ToolbarRadioGroup class is exported), since ToolbarButton/ToolbarToggleButton/ToolbarRadioButton all share the identical fui-Button class.
ToolbarButtonDriverToolbarButtonPlain native <button class="fui-Button"> β€” no fui-ToolbarButton class of its own; delegates wholesale, like ButtonDriver.
ToolbarDividerDriverToolbarDividerDelegates to DividerDriver wholesale (identical DOM, no separate class). Orientation is INVERTED relative to the toolbar β€” verified against source (vertical: !toolbarContext.vertical): a horizontal toolbar's divider itself reports 'vertical'.
ToolbarRadioGroupDriverToolbarRadioGroupToolbarRadioGroup IS ToolbarGroup with role="radiogroup" forced on, sharing its class β€” no native :checked/[value=] to delegate to HTMLRadioButtonGroupDriver, so this is a ListComponentDriver over role="radio" buttons instead (mirrors TabListDriver's shape).
ToolbarRadioButtonDriverToolbarRadioButtonReal <button role="radio" aria-checked> β€” Fluent explicitly strips the aria-pressed its shared toggle-button primitive would otherwise carry. setSelected(false) is rejected, same contract as RadioDriver.
OverflowDriverOverflowOverflow/OverflowItem render NO wrapper of their own β€” they clone ref/class/data-* attributes onto the consumer's OWN child, so the scene locator must target that element directly, assuming the idiomatic flat-row usage (every item a direct child of the wrapped container). getOverflowMenu() returns a plain Wave 2 MenuDriver resolved via [data-overflow-menu] β€” no new portal logic needed, since useOverflowMenu() wires a trigger but builds no Menu of its own.
OverflowItemDriverOverflowItemisOverflowing() reads the data-overflowing attribute directly (portable/jsdom-safe) rather than relying on computed CSS visibility, since overflowed items stay mounted (display: none via a stylesheet rule) rather than being removed.

Wave 5 β€” data display & feedback: presentational/read-state drivers β€” the fastest wave per-component (few new interactions, no portals except InfoButton's own inline-by-default popover). A unifying design rule shapes several of this wave's composite components: CardDriver folds CardHeader/CardFooter/CardPreview reads directly onto itself (mirroring DialogDriver/TeachingPopoverDriver's "one composite driver" shape from Wave 2), rather than minting a driver class per sub-component β€” none of the three are independently addressable/interactive the way a repeated list item (AvatarGroupItem, ListItem) is.

DriverFluent componentNotes
AvatarDriverAvatarrole="img" aria-label="{name}"; getName() reads that aria-label (present regardless of whether a badge is also set). getInitials() reads the fui-Avatar__initials part, undefined when an image renders instead. getPresenceBadge() returns a nested PresenceBadgeDriver for the fui-Avatar__badge part.
AvatarGroupDriverAvatarGroupA ListComponentDriver over AvatarGroupItem wrappers (NOT the nested .fui-Avatar elements directly β€” each is the sole avatar under its own wrapper, so :nth-of-type addressing on the avatar itself would miscount; see AvatarGroupItemDriver). Overflow (AvatarGroupPopover, the +N trigger) is out of scope β€” only inline items are enumerated.
AvatarGroupItemDriverAvatarGroupItemWrapper with no state of its own; getAvatar() returns the nested AvatarDriver.
BadgeDriverBadgePlain <div class="fui-Badge">; all state is its own text content (inherited getText()) β€” appearance/color/shape/size have no un-hashed DOM reflection.
CounterBadgeDriverCounterBadgeExtends BadgeDriver wholesale (identical DOM shape plus a marker class). getDisplayedCount() reads the already-overflowCount-clamped text (e.g. count={150} with the default overflowCount={99} renders "99+").
PresenceBadgeDriverPresenceBadgerole="img" aria-label="{status}" β€” for a plain status the label IS the status string; getStatusLabel() reads it raw (the outOfOffice composite label text is unverified β€” see Known gaps).
CardDriverCard (+ CardHeader/CardFooter/CardPreview)Folds header/footer/preview reads directly (see the wave-level rule above). A selectable card (selected/onSelectionChange supplied) renders a REAL native <input type="checkbox" class="fui-Card__checkbox">, driven directly via Interactor.isChecked/click. isDisabled() reads the root's aria-disabled (present regardless of selectable).
PersonaDriverPersonagetPrimaryText/getSecondaryText/getTertiaryText read their own fui-Persona__* structural classes (the whole-root getText() would double-count the avatar's own initials). getAvatar()/getPresenceBadge() reach the avatar or (in presenceOnly mode) the standalone presence slot.
ListDriverListA ListComponentDriver over .fui-ListItem children β€” homogeneous siblings regardless of root tag (<ul>/<ol>/<div>) or role (role="list" plain, role="listbox" when selectionMode is set). getSelectedValues() enumerates selected items' values.
ListItemDriverListItemIts value prop reflects directly onto a plain value attribute (a rare un-hashed prop reflection in this package). isSelected() reads aria-selected, present only when the parent's selectionMode is set.
SkeletonDriverSkeleton (+ SkeletonItem)role="progressbar" aria-busy="true"; individual items carry zero distinguishable state, so this driver exposes only getItemCount() rather than a per-item class (same "no interactive unit" call Wave 4 makes for BreadcrumbDivider).
SpinnerDriverSpinnerrole="progressbar"; getLabel() reads the fui-Spinner__label part, undefined when rendered without one.
ProgressBarDriverProgressBarrole="progressbar" with aria-valuemin/aria-valuemax/aria-valuenow; aria-valuenow is entirely ABSENT (not "0") in the indeterminate (no value prop) state β€” isIndeterminate() reads that absence directly.
InfoLabelDriverInfoLabelThe scene locator lands on the inner <label> (InfoLabel's PRIMARY slot), not the wrapping <span> β€” extends LabelDriver wholesale. getInfoButton() reaches the sibling InfoButton via an ancestor :has() re-root (same idiom as SearchBoxDriver's wrapper).
InfoButtonDriverInfoButtonUnlike every other Popover-backed overlay in this package, InfoButton's inline prop defaults to true β€” its popover surface renders as a plain DOM SIBLING (adjacent-sibling-combinator addressed), no portal re-root needed. Targets only that default case; inline={false} (portal) is out of scope.
MessageBarDriverMessageBar (+ MessageBarBody/Title/Actions)getTitle() reads fui-MessageBarTitle exactly; getBodyText() reads the WHOLE fui-MessageBarBody, which includes the title's text too since Title nests INSIDE Body (known gap, same class as CompoundButtonDriver's). No built-in dismiss β€” declare the consumer-supplied action as its own scene part, same as ToastDriver.
AlertDriverAlertDeprecated by Fluent itself (AlertProps's own TSDoc: "use the Toast or MessageBar component") and importable ONLY from @fluentui/react-components/unstable β€” NOT the stable package surface. getText() includes an action button's text when present (no isolated message read; known gap).

Wave 6 β€” complex/composite (the highest-engineering-cost wave, saved for last by design): Table/DataGrid β€” the single largest driver family in this catalog, split the same way component-driver-mui-v9's Table family is (a top-level driver over rows, rows over cells, plus a distinct header-row driver) β€” Tree, and Carousel.

DriverFluent componentNotes
TableDriverTableA ListComponentDriver over data (body) rows (.fui-TableBody .fui-TableRow), templated off component-driver-mui-v9's TableDriver. Header row is a separate TableHeaderRowDriver rather than folded in β€” a real DOM difference from MUI (Fluent's TableCell/TableHeaderCell are two structurally distinct components, <td>/<th>, each with its own structural class), not a stylistic choice. sortByColumn/getSortDirection are generic/best-effort (read aria-sort, click the header cell) since plain Table ships no built-in sort orchestration.
TableRowDriverTableRow (body)A ListComponentDriver over .fui-TableCell children.
TableHeaderRowDriverTableRow (header)A ListComponentDriver over .fui-TableHeaderCell children; adds best-effort getSortDirection/sortByColumn.
TableRowDriverBaseshared baseListComponentDriver-based cell iteration (getCellCount/getCell/getCellTexts) shared by both row kinds above.
TableCellDriverTableCellPlain native <td>, no role; relies on inherited getText(). getActionButtons()/isActionsVisible() read a nested TableCellActions (shared logic with DataGridCellDriver) β€” see Wave 6 scope decisions.
TableHeaderCellDriverTableHeaderCellPlain native <th>; isSortable()/getSortDirection() read aria-sort (absent entirely, not "none", when not sortable).
DataGridDriverDataGridA ListComponentDriver over data rows (.fui-DataGridBody [role="row"]) with built-in sort, row selection (single/multiselect), and column resize β€” the richer columns/items-driven sibling of Table. Columns are addressed by zero-based index, not field β€” columnId has zero DOM reflection (verified: getNativeElementProps's per-tag attribute allowlist strips it from every rendered slot).
DataGridRowDriverDataGridRow (data)A childListHelper-based row over .fui-DataGridCell children (not ListComponentDriver β€” DataGridRow conditionally renders a leading DataGridSelectionCell only when selectionMode is set, which breaks :nth-of-type addressing). Implements IToggleDriver; setSelected clicks the row's real native checkbox/radio.
DataGridHeaderRowDriverDataGridRow (header)Same childListHelper shape over [role="columnheader"] children; adds sort, "select all" (multiselect only), and per-column resize delegation.
DataGridRowDriverBaseshared basechildListHelper-based cell iteration shared by both DataGrid row kinds β€” diverges from TableRowDriverBase for the reason above.
DataGridCellDriverDataGridCellrole="gridcell" div; relies on inherited getText(). getActionButtons()/isActionsVisible() read a nested TableCellActions (shared logic with TableCellDriver) β€” see Wave 6 scope decisions.
DataGridHeaderCellDriverDataGridHeaderCellExtends TableHeaderCellDriver (identical aria-sort surface β€” renderDataGridHeaderCell_unstable delegates to renderTableHeaderCell_unstable); adds getWidthPx()/resize() for resizableColumns, plus pressResizeKey()/isInKeyboardResizeMode() for the keyboard-accessible resize path β€” see Wave 6 scope decisions.
TreeDriverTreeNever portals. A ListComponentDriver over this level's TOP-LEVEL TreeItems only β€” .fui-TreeItem recurs at every nesting level, so the item locator is 'Child'-restricted (a plain descendant match would flatten the whole tree). Recursion into a branch's own children is TreeItemDriver.getChildItems()'s job, not this driver's. isMultiSelect() reads aria-multiselectable (present only for selectionMode="multiselect"; absent, not "false", for "single"/"none"). Covers nested Tree/TreeItem β€” see FlatTreeDriver below for the flattened, virtualization-friendly variant.
TreeItemDriverTreeItem (+ TreeItemLayout)role="treeitem"; own root, no wrapper. getLabel() reads .fui-TreeItemLayout__main specifically β€” the inherited whole-row getText() would include an expanded branch's descendant labels too (same class of hazard as CompoundButtonDriver). isBranch()/isLeaf() read aria-expanded's PRESENCE (itemType is consumer-declared, not inferred from children). isSelected() reads aria-selected (single) or aria-checked (multiselect) β€” never both, neither under "none". select()/setSelected() click the item's own real-but-aria-hidden selector input directly, not the row β€” verified this does NOT also toggle expansion, unlike component-driver-mui-x-v9's SimpleTreeViewDriver. setSelected(false) rejected in single-select mode (native radio semantics), same contract as RadioDriver. getValue() reads the DOM-reflected data-fui-tree-item-value. Does not implement IDisableableDriver β€” TreeItem has no disabled prop at all. getChildItemCount()/getChildItemByIndex()/getChildItems() recurse one level down and deliberately do NOT auto-expand a collapsed branch β€” call expand() first.
FlatTreeDriverFlatTreeNever portals. FlatTreeItems never nest in the DOM β€” DOM audit (@fluentui/react-tree@9.16.3): every visible item, at any aria-level, is a direct 'Child' of this same root, so the inherited ListComponentDriver reads (getItemCount/getItemByIndex/getItems/getItemByLabel) already answer "every item, any level" with no override, unlike TreeDriver's deliberate top-level-only restriction. Adds getItemByValue() β€” a flat dataset's natural lookup key, since visible labels commonly repeat across branches.
FlatTreeItemDriverFlatTreeItem (+ TreeItemLayout)A bare re-export of TreeItem upstream (export const FlatTreeItem = TreeItem;), so it shares TreeItemDriver's locators and most read/selection logic (getLabel/isBranch/isLeaf/isExpanded/isSelected/setSelected/select/getValue/click). Adds getLevel()/getPosInSet()/getSetSize() from the required aria-level/aria-posinset/aria-setsize triad (FlatTreeItem warns in dev mode if any is missing). No getChildItem* recursion β€” a FlatTreeItem's "children" are other flat-list entries with a higher level, not a DOM descendant relationship (parentValue is a React-only prop; source audit confirms it never reaches the DOM), matching component-driver-mui-x-v9's SimpleTreeViewDriver precedent. expand()/collapse() need no exit-motion wait, unlike TreeItemDriver β€” there is no per-item subtree to unmount; hiding descendants is the app's own synchronous re-render.
CarouselDriverCarousel (+ CarouselViewport/CarouselSlider)A ListComponentDriver over .fui-CarouselCard children; root has no aria-roledescription (unlike the ARIA APG pattern). getNav() resolves an optional CarouselNavDriver. scrollNext/scrollPrev resolve the prev/next CarouselButtons by DOM position β€” Fluent reflects neither navType nor a default aria-label β€” and are E2E-only, like component-driver-astryx's own Carousel, since embla-carousel's snap-group computation needs real layout (see Known gaps).
CarouselCardDriverCarouselCardA slide's content; mostly the inherited getText()/exists() (identity-only, like component-driver-mui-v9's TableCellDriver). isActive() reads the non-visible-card aria-hidden signal β€” E2E-only, never toggles under jsdom at all.
CarouselNavDriverCarouselNavA ListComponentDriver over role="tab" CarouselNavButtons (same ARIA shape TabListDriver covers). Item count/getActiveIndex/selectByIndex are E2E-only β€” jsdom collapses every card count to exactly one snap group/nav button.
CarouselNavButtonDriverCarouselNavButtonA nav dot; isSelected/select mirror TabDriver's role="tab" contract (not an IToggleDriver β€” can't deselect). getLabel() reads the consumer-supplied aria-label (Fluent renders no visible text).

Wave 6 scope decisions (per the umbrella issue's "no silent caps" instruction):

  • Column resize IS in scope for DataGridDriver, despite being implemented internally via useTableColumnSizing_unstable β€” verified resizableColumns/columnSizingOptions/onColumnResize are plain, non-_unstable-suffixed fields on the stable DataGridProps type (@fluentui/react-table@9.19.17). This differs from Wave 5's AlertDriver, which is importable ONLY from the /unstable subpath with no stable entry point at all β€” an internal _unstable implementation detail is not the same as an unstable public surface. DataGridHeaderCellDriver.resize() drags the rendered .fui-TableResizeHandle (mouse-based, same technique component-driver-mui-x-v9's DataGridPremiumDriver.resizeColumn uses) β€” E2E-only, since jsdom has no layout engine. The LAST column has no resize handle by default (autoFitColumns: true absorbs remaining width) β€” resize()/resizeColumn() return false there rather than throwing.
  • Keyboard-accessible resize (useKeyboardResizing) is covered by pressResizeKey()/isInKeyboardResizeMode(), but deliberately as LOW-LEVEL primitives only β€” full source audit of @fluentui/react-table@9.19.17 confirms entering keyboard-resize mode (columnSizing_unstable.enableKeyboardMode) has NO default UI trigger anywhere in the library (Fluent's own Storybook wires it via a consumer-added context menu); nothing calls it internally. A driver method cannot invoke an app-specific affordance that doesn't exist in Fluent's own DOM, so pressResizeKey() assumes the resize handle is already focused (via whatever entry point the consuming app wired) and dispatches ArrowLeft/ArrowRight/Enter/Space/Escape on it directly β€” verified via a rendered probe that this reaches useKeyboardResizing's internal handler correctly (confirmed through onColumnResize firing with the expected delta). Like the mouse-drag path, the resulting width change is E2E-only under jsdom.
  • Cell editing is NOT a Fluent DataGrid feature at all β€” confirmed @fluentui/react-table@9.19.17 ships no editing capability upstream (unlike MUI-X Premium's startCellEdit/commitCellEdit); not a driver gap.
  • TableCellActions (getActionButtons()/isActionsVisible() on TableCellDriver/DataGridCellDriver) ships with NO default hover/focus CSS of its own either β€” DOM/source audit of useTableCellActionsStyles_unstable shows visibility is 100% driven by the component's own visible boolean prop, which the consuming app must wire from whatever hover/focus state it tracks. isActionsVisible() reads the actual computed opacity rather than matching a Griffel-hashed class name, so it reflects real visibility however the app wired it.
  • No Fluent-native pagination component exists alongside Table/DataGrid β€” pagination is entirely a consumer concern; out of scope, not a gap (no TablePaginationDriver equivalent).
  • No built-in filtering, row grouping, virtualization, or column pinning on DataGrid β€” Fluent's own DataGrid ships none of these (MUI-X Pro/Premium-only features); not driver gaps.
  • Tree/TreeItem and FlatTree/FlatTreeItem are both covered β€” the latter landed as a follow-up (issue #1138) once its own DOM audit confirmed useHeadlessFlatTree_unstable (still _unstable-suffixed as of @fluentui/react-tree@9.16.3) produces a structurally flat, non-nesting DOM needing its own driver pair (FlatTreeDriver/FlatTreeItemDriver) rather than reusing TreeDriver/TreeItemDriver's recursive shape.
  • Only Carousel/CarouselCard/CarouselNav(+CarouselNavButton) are covered; CarouselAutoplayButton is out of scope (not named in the umbrella issue).

Known gaps (Wave 1)​

  • CompoundButtonDriver.getSecondaryContent() returns the secondary line, but there is no way to read only the primary label β€” the two render as adjacent text nodes in one container, and CSS/textContent cannot exclude nested content from an ancestor read.

Known gaps (Wave 2)​

  • No portable closeByBackdropClick for Dialog/OverlayDrawer: their backdrops are separate document.body siblings of the surface (not descendants), with no id/data-* link back to a specific instance β€” a backdrop click cannot be scoped to one dialog when more than one modal is open. Only closeByEscape is offered.
  • Escape dismisses the topmost stacked overlay, not a specific targeted one β€” see the portal & overlay recipe above. Applies to Dialog, Popover, Menu, and OverlayDrawer.
  • TooltipDriver's "label"-relationship fallback is best-effort with multiple tooltips mounted: isOpen/open/dismiss/waitForOpen/waitForClose fall back to matching any role="tooltip" on the page when the trigger carries no aria-describedby link (the default relationship="label" mode) β€” verified against real Chromium, with two tooltips mounted the portalled contents do not land in the DOM in trigger-declaration order, so this fallback can resolve to the wrong trigger's content. getContent() is unaffected (it reads the trigger's own aria-label directly). relationship="description" tooltips do not have this problem β€” their aria-describedby link always resolves the exact instance.
  • OverlayDrawer's defaultOpen is deprecated and non-functional (a Fluent-side limitation, not a driver one) β€” drive it via the controlled open prop.

Known gaps (Wave 3)​

  • No selectByValue/getOptionValues on ComboboxDriver β€” Fluent never reflects Option's value prop to the DOM (only the visible label renders); selectByLabel/getOptionByLabel only.
  • multiselect mode is out of scope for ComboboxDriver and DropdownDriver β€” both swap every option's role/state model entirely (menuitemcheckbox/aria-checked instead of option/aria-selected), which these drivers' selectors don't match.
  • DropdownDriver.getSelectedLabel() cannot distinguish a placeholder from a genuinely-selected option sharing its label β€” both render through the identical trigger text node while closed, with no other DOM signal available.
  • SpinButtonDriver.setValue() is not clamped to min/max β€” Fluent commits an out-of-range typed value verbatim; the returned boolean reflects "did the exact value land," not "was it within bounds."
  • RatingDriver/SwatchPickerItemDriver reject clearing a selection β€” native radio-group semantics (Rating's hidden radios; SwatchPicker's radiogroup/grid) offer no click path back to "unselected," mirroring RadioDriver's existing setSelected(false) rejection.
  • RatingDisplayDriver.getMax() is exact only in the default display mode β€” compact mode always renders a single icon regardless of max, so a compact instance's true max isn't observable in the DOM.
  • No getValue/selectByValue on SwatchPickerDriver/SwatchPickerItemDriver β€” ColorSwatch.value/SwatchPicker.selectedValue have zero DOM reflection; color-based equivalents (getColor/selectByColor) are exposed instead.
  • TagDriver does not implement IDisableableDriver β€” a static Tag's disabled prop has no DOM reflection at all (its root is a plain <span>, not a button); InteractionTagDriver/TagGroupDriver remain reliable since their roots reflect it natively.
  • TagPickerOptionDriver has no portable isSelected β€” neither an open-list option nor a selected tag exposes a computed selection signal under Fluent's idiomatic (filter-selected-out) usage pattern.
  • No TagPickerOptionGroup support on TagPickerDriver β€” only a flat, ungrouped option list is enumerated.
  • No alpha-channel slider on ColorPickerDriver β€” @fluentui/react-color-picker ships no alpha channel in this version; an upstream library gap, not a driver omission.
  • No keyboard/drag write path on ColorAreaDriver/ColorSliderDriver/SwatchPickerDriver β€” setRangeValue/click already give an exact, cross-environment-verified write, so nudge/drag primitives were left out as redundant surface rather than a missing capability.

Known gaps (Wave 4)​

  • AccordionItemDriver.collapse() is a no-op unless the parent Accordion sets collapsible β€” verified against Fluent's own updateOpenItems source: with the default collapsible={false}, clicking the header of the single currently-open item (in single-select mode, or the last remaining open item in multiple mode) is silently ignored, since Fluent refuses to ever reach zero open items otherwise. expand() is unaffected.
  • No BreadcrumbDividerDriver β€” BreadcrumbDivider is purely decorative (aria-hidden, no interactive state); out of scope per the wave-level "interactive units only" rule (see above the Wave 4 table).
  • NavDrawerDriver only covers the default portal-backed variant β€” type="inline" switches NavDrawer's underlying element to a non-portal InlineDrawer, which this driver's class-based re-root does not resolve (the same asymmetry OverlayDrawerDriver/InlineDrawerDriver are split into two classes over elsewhere in this package).
  • No portable isDisabled() on NavItemDriver/NavCategoryItemDriver β€” Fluent's @fluentui/react-nav types expose no confirmed disabled prop/DOM reflection for these components to verify against.
  • OverflowDriver assumes the idiomatic flat-row usage β€” every OverflowItem's underlying element must be a DIRECT child of the container the scene locator targets; a consumer nesting items inside extra wrapper elements needs a different container locator.
  • Real overflow computation (isOverflowing(), which items land behind the "+N" trigger) needs actual layout measurement (@fluentui/priority-overflow's ResizeObserver-driven algorithm), unavailable under jsdom (confirmed: it degrades gracefully there β€” no ResizeObserver global logs a console error and no-ops, it does not throw β€” but produces no meaningful overflow state). This package's shared dom+e2e suite pattern runs identical assertions in both environments, so OverflowDriver's own test coverage is limited to layout-independent behavior (item registration, labels, count, the menu's structural composition); genuine overflow-triggering behavior needs a dedicated real-browser scenario outside this package's shared-suite pattern to verify meaningfully.
  • @fluentui/react-nav (Nav/NavDrawer) is explicitly pre-release upstream β€” per the package's own README ("not production-ready... APIs might change before final release"); NavDriver/NavDrawerDriver/NavItemDriver/NavCategoryItemDriver target the current shipped shape and may need to track a future breaking change.

Known gaps (Wave 5)​

  • AvatarGroupDriver does not enumerate overflowed items β€” only the inline AvatarGroupItems are covered; items pushed into the AvatarGroupPopover's +N trigger are out of scope for this wave.
  • PresenceBadgeDriver.getStatusLabel()'s outOfOffice composite label text is unverified β€” confirmed against real DOM only for a plain status (where the aria-label is exactly the status string); the exact label Fluent renders when outOfOffice is also set was not probed, so this driver exposes the raw aria-label rather than a parsed, typed result.
  • No getValue/read path for Card's selected beyond the boolean isSelected() β€” matches the component's own API surface (no intermediate/tri-state selection exists).
  • MessageBarDriver.getBodyText() includes the title's text β€” MessageBarTitle nests INSIDE MessageBarBody rather than beside it, so there is no CSS/textContent read that excludes it; use getTitle() for the title alone. Same class of limitation as CompoundButtonDriver.getSecondaryContent().
  • AlertDriver.getText() includes the action button's text when one is present β€” the message renders as a bare text node alongside the icon/action as DOM siblings, with no wrapping element around the message alone to read in isolation.
  • InfoButtonDriver/InfoLabelDriver.getInfoButton() only cover the default inline={true} popover β€” inline={false} switches InfoButton to the standard portal-to-document.body behavior used elsewhere in this package (Wave 2), which this driver does not resolve.
  • ListItemDriver.isSelected() is only meaningful when the parent List has a selectionMode β€” aria-selected is entirely absent (not "false") otherwise, and this driver treats that absence the same as "not selected".
  • SkeletonDriver exposes no per-item driver β€” individual SkeletonItems carry no distinguishable state (no reflected shape/animation prop, no text) beyond their count.

Known gaps (Wave 6)​

  • DataGridRowDriver.setSelected(false) silently no-ops in single-select mode, unlike RadioDriver/TreeItemDriver's throw-on-deselect contract β€” verified against @fluentui/react-utilities's useSingleSelection.toggleItem, which always resolves a click to "select this row," never "deselect," so a no-op (not an exception) keeps DataGridDriver.deselectRow's bulk-friendly boolean-return contract intact for an otherwise-valid row index.
  • Tree selection is fully controlled upstream β€” a consumer scene MUST wire checkedItems/onCheckedChange for TreeItemDriver.select()/setSelected() to have any visible effect. Source audit of @fluentui/react-tree@9.16.3: unlike openItems (which has a genuine uncontrolled fallback), checkedItems is derived only from the checkedItems prop with no internal state and no defaultCheckedItems on the base <Tree> β€” a bare selectionMode="single"/"multiselect" Tree with nothing wired never fires onCheckedChange and stays silently unselectable no matter what's clicked or key-pressed. Not a driver limitation; see Tree.examples.tsx for the required controlled-state pattern.
  • TreeItemDriver does not implement IDisableableDriver β€” TreeItem has zero disabled support (confirmed absent from the compiled TreeItemProps type declarations), unlike AccordionItem.
  • TreeDriver.getItemByLabel() (inherited from ListComponentDriver) compares against getText(), which for a currently-expanded branch includes its descendant items' labels too (same class of limitation as CompoundButtonDriver/MessageBarDriver) β€” prefer getItems() + TreeItemDriver.getLabel() for a reliable, scoped comparison.
  • TreeItemDriver.getChildItemCount()/getChildItemByIndex()/getChildItems() never auto-expand a collapsed branch β€” a deliberate departure from NavCategoryItemDriver.getSubItemCount()'s auto-expand, to keep these reads free of UI side effects; call expand() first.
  • TreeItemPersonaLayout is unaudited β€” TreeItemDriver.getLabel() (and FlatTreeItemDriver.getLabel(), which shares the same locator) assumes TreeItemLayout's .fui-TreeItemLayout__main structural class; a TreeItemPersonaLayout-based item's label read is unverified.
  • FlatTreeItemDriver exposes no descendant-navigation method (no getChildItem*, unlike TreeItemDriver) β€” a flat item's hierarchy is knowable only from getLevel()/getPosInSet()/getSetSize(); the DOM carries no parent/child relationship to walk (parentValue is consumed purely as a React prop upstream and never reaches the DOM β€” confirmed via source audit of getIntrinsicElementProps/getNativeElementProps's attribute allowlist). A caller wanting a specific descendant looks it up by value via FlatTreeDriver.getItemByValue() instead.
  • FlatTree selection has the identical fully-controlled-upstream caveat as Tree (see above) β€” useHeadlessFlatTree_unstable's options DO support real uncontrolled checkedItems via defaultCheckedItems (unlike the bare <Tree checkedItems> prop), so this only bites a scene that skips the headless hook and hand-assembles <FlatTree> props itself.
  • Carousel cannot mount under jsdom without extra polyfills β€” embla-carousel@8.6.0 (the engine @fluentui/react-carousel@9.9.10 wraps) unconditionally calls window.matchMedia and constructs an IntersectionObserver during its first activation effect, which jsdom implements neither of β€” package-tests/component-driver-fluent-v9-test/jest.setup.ts installs guarded, inert stubs for both, alongside the package's existing ResizeObserver stub.
  • Real navigation/active-slide state on Carousel is E2E-only, not merely imprecise, under jsdom β€” even with the mount-blocking polyfills in place, CarouselNav still renders exactly ONE CarouselNavButton regardless of the real card count, and both prev/next CarouselButtons stay permanently disabled. CarouselNavDriver's item surface, CarouselDriver.scrollNext/scrollPrev, and CarouselCardDriver.isActive() are all E2E-only for this reason.
  • No default accessible name on CarouselButton, and its navType prop is not reflected to the DOM at all β€” scrollPrev/scrollNext resolve the pair by DOM position (first = prev, last = next) among descendant .fui-CarouselButton elements; a consumer rendering only one such button, or in reverse order, is not resolved correctly (same class of assumption as OverflowDriver's documented flat-row usage).
  • CarouselNavImageButton (an alternate image-thumbnail nav button, also role="tab") has no dedicated example/test coverage this wave, though CarouselNavButtonDriver's generic surface should apply to it structurally.

Classes​

Interfaces​

Type Aliases​

Variables​