Semantic matching basics
Describe your idea
This is an issue meant to specifically hold the idea mentioned here: https://github.com/wix/Detox/issues/4145#issuecomment-1689920814
We want to allow for a more intuitive matching API that would allow for a middle-ground solution between the "traditional" matching techniques (e.g. test ID / label based) and the fully blown Detox pilot. Meaning:
by.type('list')// or horizontal-list / vertical-list, or all of thoseby.type('image')// or iconby.type('input-field')
etc.
- TODO: This kind of an API really raises the need for an easier disambiguation. This ideas could be introduced further down the road based on more discussions in the original issue (#4145)
- Also related: #4686
Proposal about possible semantic UI types
Here is my attempt to outline semantic UI types relevant for mobile E2E testing (e.g. with Detox). Below you can find primary names, common aliases, source references (ARIA, HIG, Material, Open UI), and typical mappings to React Native, Android, iOS components, and accessibility roles.
@markdevocht @d4vidi
📄 Static Content
-
text Aliases: label, static-text Sources: ARIA
text, iOSUILabel, AndroidTextViewRN:Text,RCTText,ReactTextViewa11y:accessibilityRole="text",StaticTextin XCUI -
image Aliases: image-view, icon Sources: ARIA
img, iOSUIImageView, AndroidImageViewRN:Image,RCTImageView,ReactImageViewa11y:accessibilityRole="image",Imagein XCUI
✍️ Input Controls
-
text-field Aliases: input, textbox Sources: ARIA
textbox, HIGUITextField, AndroidEditTextRN:TextInput,RCTTextInput,ReactEditTexta11y:accessibilityRole="textbox",TextFieldin XCUI -
slider Aliases: seek-bar, range-slider Sources: ARIA
slider,UISlider, AndroidSeekBarRN:Slider,RCTSlider,ReactSlidera11y:accessibilityRole="adjustable",Adjustablein XCUI -
stepper Aliases: incrementor Sources: HIG
UIStepper, Android+/- Buttonpattern RN: custom composed buttons a11y: grouped buttons withaccessibilityLabeloradjustable -
date-picker Aliases: time-picker, wheel-picker Sources: HIG
UIDatePicker, AndroidDatePickerDialogRN:@react-native-community/datetimepickera11y: role varies, useaccessibilityLabel, XCUI showsDate Picker -
search-field Aliases: search-box Sources: ARIA
searchbox, iOSUISearchBar, AndroidSearchViewRN:TextInputwithaccessibilityRole="search"a11y:SearchFieldin XCUI
🔘 Selection Controls
-
button Aliases: action-button, submit Sources: ARIA
button, iOSUIButton, AndroidButtonRN:Button,TouchableOpacity,RCTButtona11y:accessibilityRole="button",Buttonin XCUI -
link Aliases: hyperlink Sources: ARIA
linkRN:TextwithonPress,accessibilityRole="link"a11y:Linkin XCUI -
switch Aliases: toggle, on-off Sources: ARIA
switch, iOSUISwitch, AndroidSwitchCompatRN:Switch,RCTSwitch,ReactSwitcha11y:accessibilityRole="switch",Switchin XCUI -
checkbox Aliases: check-box Sources: ARIA
checkbox, AndroidCheckBox, custom iOS RN:@react-native-community/checkboxor custom a11y:accessibilityRole="checkbox",CheckBoxin XCUI -
radio-button Aliases: option-button Sources: ARIA
radio, AndroidRadioButton, iOS custom or segmented RN:RadioButton,SegmentedControlIOSa11y:accessibilityRole="radio",RadioButtonin XCUI -
picker Aliases: selector, spinner, dropdown Sources: ARIA
combobox, iOSUIPickerView, AndroidSpinnerRN:Picker,RCTPicker,ReactPickerManagera11y:accessibilityRole="menu",Pickerin XCUI
🧩 Containers & Collections
-
scrollview Aliases: scroll-container Sources: iOS
UIScrollView, AndroidScrollViewRN:ScrollView,FlatList,SectionLista11y:adjustable, or no role -
list Aliases: table-view, recycler-view Sources: ARIA
list, iOSUITableView, AndroidRecyclerViewRN:FlatList,SectionList,RCTScrollViewa11y:accessibilityRole="list",Listin XCUI -
grid Aliases: collection-view Sources: ARIA
grid, iOSUICollectionView, AndroidGridLayoutRN: customFlatListwith numColumns > 1 a11y:accessibilityRole="grid",CollectionViewin XCUI -
card Aliases: tile, panel Sources: Material Design, Open UI RN: View with border/shadow, accessible via label a11y: grouped container, use
accessibilityHint -
form Aliases: form-group Sources: ARIA
formRN:Viewwith groupedTextInputs a11y:accessibilityRole="form" -
header Aliases: section-header, title Sources: ARIA
heading, iOS trait, AndroidTextViewstyled RN:TextwithaccessibilityRole="header"a11y:Headerin XCUI -
toolbar Aliases: nav-bar, top-app-bar Sources: ARIA
toolbar, iOSUINavigationBar, AndroidToolbarRN:react-navigationheader, or custom a11y:accessibilityRole="toolbar"
🧭 Navigation
-
tab Aliases: tab-item, tab-bar-button Sources: ARIA
tab, iOSUITabBarItem, AndroidTabLayoutRN:react-navigationTabNavigator a11y:accessibilityRole="tab",Tabin XCUI -
menu Aliases: dropdown-menu, overflow-menu, popup-menu Sources: ARIA
menu, AndroidPopupMenu, iOSUIMenuRN: custom modal list, orreact-native-popup-menua11y:accessibilityRole="menu",Menuin XCUI -
drawer Aliases: side-menu, navigation-drawer Sources: Material Design
DrawerLayout, iOS custom RN:react-navigationDrawerNavigator a11y:accessibilityRole="menu",Drawerin XCUI
📢 Feedback & Dialogs
-
dialog Aliases: modal, alert-dialog Sources: ARIA
dialog, iOSUIAlertController, AndroidAlertDialogRN:Modal,react-native-dialoga11y:accessibilityRole="dialog",Dialogin XCUI -
alert Aliases: toast, banner, snackbar Sources: ARIA
alert, AndroidToast, iOSBanner,SnackbarRN:ToastAndroid,react-native-snackbara11y:accessibilityRole="alert", orAnnouncementin XCUI -
progress-indicator Aliases: spinner, progress-bar Sources: ARIA
progressbar, iOSUIActivityIndicatorView, AndroidProgressBarRN:ActivityIndicator,ProgressBarAndroid,RCTActivityIndicatorViewa11y:accessibilityRole="progressbar",Progress Indicatorin XCUI
🧷 Minor Elements
-
tooltip Aliases: helper-text, hint Sources: ARIA
tooltip, iOSaccessibilityHint, AndroidcontentDescriptionRN:accessibilityHintprop a11y:Hintin XCUI (no direct element) -
invisible-overlay Aliases: hit-area, tap-region Sources: gesture design patterns RN:
View,Pressablewithopacity: 0, often used for custom gestures a11y: may needaccessible={false}or explicit labeling
Need to check the Android inheritance is found on custom components.
Here's the breakdown for the Android class name search.
The fun isOfClassName(className: String): Matcher<View> in ViewMatchers.kt uses
private class IsAssignableFromMatcher(private val clazz: Class<*>) : TypeSafeMatcher<View>() which in turn calls
clazz.isAssignableFrom(view.javaClass)
which works as follows:
The Class.isAssignableFrom() method in Java/Kotlin returns true if:
- The class is exactly the same class
- The class is a superclass of the target class
- The class is a superinterface implemented by the target class
So this means that when searching for a 'list' in the semantic matching, any custom component inherited from the described components ['android.widget.ListView', 'androidx.recyclerview.widget.RecyclerView', 'com.facebook.react.views.scroll.ReactScrollView'] will be matched.
Here's the breakdown for the Android class name search.
The
fun isOfClassName(className: String): Matcher<View>in ViewMatchers.kt usesprivate class IsAssignableFromMatcher(private val clazz: Class<*>) : TypeSafeMatcher<View>()which in turn callsclazz.isAssignableFrom(view.javaClass)which works as follows:The Class.isAssignableFrom() method in Java/Kotlin returns true if:
* The class is exactly the same class * The class is a superclass of the target class * The class is a superinterface implemented by the target classSo this means that when searching for a 'list' in the semantic matching, any custom component inherited from the described components
['android.widget.ListView', 'androidx.recyclerview.widget.RecyclerView', 'com.facebook.react.views.scroll.ReactScrollView']will be matched.
thanks for looking into it!