diff --git a/.changeset/android-tab-test-ids.md b/.changeset/android-tab-test-ids.md new file mode 100644 index 00000000..d6ff04db --- /dev/null +++ b/.changeset/android-tab-test-ids.md @@ -0,0 +1,6 @@ +--- +"react-native-bottom-tabs": patch +"@bottom-tabs/react-navigation": patch +--- + +Expose Android tab button test IDs to UIAutomator-based E2E tools. diff --git a/apps/example/e2e/native-tabs-test-id.yaml b/apps/example/e2e/native-tabs-test-id.yaml new file mode 100644 index 00000000..2f34fc3e --- /dev/null +++ b/apps/example/e2e/native-tabs-test-id.yaml @@ -0,0 +1,20 @@ +appId: bottomtabs.example +name: Navigate tabs via testIDs +--- +- launchApp +- scrollUntilVisible: + element: + text: ^Native Bottom Tabs$ + direction: DOWN + timeout: 15000 +- tapOn: + text: ^Native Bottom Tabs$ +- assertVisible: Press me +- tapOn: + id: articleTestID +- assertVisible: What is Lorem Ipsum? +- tapOn: + id: albumsTestID +- tapOn: + id: chatTestID +- assertVisible: Press me diff --git a/apps/example/package.json b/apps/example/package.json index e1c5d607..565eab4c 100644 --- a/apps/example/package.json +++ b/apps/example/package.json @@ -7,6 +7,8 @@ "build:android": "npm run mkdist && react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist && react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a -PnewArchEnabled=true\"", "build:ios": "npm run mkdist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.ios.jsbundle --assets-dest dist", "ios": "react-native run-ios", + "e2e:ios": "maestro test --platform=ios e2e", + "e2e:android": "maestro test --platform=android e2e", "mkdist": "node -e \"require('node:fs').mkdirSync('dist', { recursive: true, mode: 0o755 })\"", "start": "react-native start" }, diff --git a/apps/example/src/Examples/NativeBottomTabs.tsx b/apps/example/src/Examples/NativeBottomTabs.tsx index f4ec24e2..47d19329 100644 --- a/apps/example/src/Examples/NativeBottomTabs.tsx +++ b/apps/example/src/Examples/NativeBottomTabs.tsx @@ -60,6 +60,7 @@ function NativeBottomTabs() { name="Albums" component={Albums} options={{ + tabBarButtonTestID: 'albumsTestID', tabBarIcon: () => require('../../assets/icons/grid_dark.png'), }} /> @@ -72,6 +73,7 @@ function NativeBottomTabs() { }, }} options={{ + tabBarButtonTestID: 'contactsTestID', tabBarIcon: () => require('../../assets/icons/person_dark.png'), tabBarActiveTintColor: 'yellow', preventsDefault: true, @@ -86,6 +88,7 @@ function NativeBottomTabs() { }, }} options={{ + tabBarButtonTestID: 'chatTestID', tabBarIcon: () => require('../../assets/icons/chat_dark.png'), tabBarActiveTintColor: 'white', }} diff --git a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt index 911d2789..3c3d4bdb 100644 --- a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt +++ b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt @@ -20,7 +20,10 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.LinearLayout import android.widget.TextView +import androidx.core.view.AccessibilityDelegateCompat import androidx.core.view.MenuItemCompat +import androidx.core.view.ViewCompat +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.core.view.forEachIndexed import coil3.ImageLoader import coil3.asDrawable @@ -281,12 +284,8 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) { onTabSelected(menuItem) } - item.testID?.let { testId -> - view.findViewById(com.google.android.material.R.id.navigation_bar_item_content_container) - ?.apply { - tag = testId - } - } + view.findViewById(com.google.android.material.R.id.navigation_bar_item_content_container) + ?.setTabTestID(item.testID) } } } @@ -301,6 +300,24 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) { return bottomNavigation.menu.findItem(index) ?: bottomNavigation.menu.add(0, index, 0, title) } + private fun View.setTabTestID(testId: String?) { + tag = testId + if (testId == null) { + ViewCompat.setAccessibilityDelegate(this, null) + return + } + + ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() { + override fun onInitializeAccessibilityNodeInfo( + host: View, + info: AccessibilityNodeInfoCompat + ) { + super.onInitializeAccessibilityNodeInfo(host, info) + info.viewIdResourceName = testId + } + }) + } + private fun updateIconTintMode(menuItem: MenuItem, item: TabInfo) { MenuItemCompat.setIconTintMode( menuItem,