Skip to content

Commit e7ef00c

Browse files
authored
chore(test): E2E test for tabBarHidden and function to select SFT screens in e2e (#3879)
## Description This PR introduces E2E Detox automation for the **test-tabs-tab-bar-hidden** scenario. Key highlights include the introduction of the selectSingleFeatureTestsScreen helper for streamlined SFT navigation and a refactor of the scrolling utility to scrollUntilVisible, enhancing support for complex view hierarchies. Closes: software-mansion/react-native-screens-labs#1118 ## Changes - new **test-tabs-tab-bar-hidden e2e** test covering manual scenario - update in **scenario.md** for this screen with information about **automation coverage** - new function **selectSingleFeatureTestsScreen** added to **e2e_utils.ts** which allow to open screen from SFT category based on scenarioGroup and screenKey values - **scrollTo** function from **e2e_utils.ts** updated to **scrollUntilVisible** and refactored to use two arguments id (of checked element) and scrolledViewId - to enable using this function inside SFT scenario groups. - **ScenarioScreen.tsx** file updated for ScenarioSelect function - new argument added - groupName - needed to refactored scrollTo function from e2e_utils.ts - **index** files were updated with testID/tabBarItemTestID/tabBarItemAccessibilityLabel needed for detox - small changes in existing **scenario.md** to apply suggestion from review - Prerequisites devices OS specified
1 parent ba243b3 commit e7ef00c

8 files changed

Lines changed: 85 additions & 17 deletions

File tree

FabricExample/e2e/e2e-utils.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import { device, expect, element, by } from 'detox';
33
export const describeIfiOS =
44
device.getPlatform() === 'ios' ? describe : describe.skip;
55

6-
async function scrollTo(id: string) {
6+
async function scrollUntilVisible(id: string, scrollViewId: string) {
77
await waitFor(element(by.id(id)))
88
.toBeVisible()
9-
.whileElement(by.id('root-screen-examples-scrollview'))
9+
.whileElement(by.id(scrollViewId))
1010
.scroll(600, 'down', Number.NaN, 0.85);
1111
}
1212

1313
export async function selectIssueTestScreen(screenName: string) {
14-
await scrollTo('root-screen-issue-tests');
14+
await scrollUntilVisible('root-screen-issue-tests', 'root-screen-examples-scrollview');
1515
await element(by.id('root-screen-issue-tests')).tap();
1616

1717
await waitFor(element(by.id('issue-tests-scrollview'))).toBeVisible();
@@ -30,3 +30,16 @@ export async function selectIssueTestScreen(screenName: string) {
3030
await expect(element(by.id(`issue-tests-${screenName}`))).toBeVisible();
3131
await element(by.id(`issue-tests-${screenName}`)).tap();
3232
}
33+
34+
export async function selectSingleFeatureTestsScreen(scenarioGroup: string, screenKey: string) {
35+
await scrollUntilVisible('root-screen-single-feature-tests', 'root-screen-examples-scrollview');
36+
await element(by.id('root-screen-single-feature-tests')).tap();
37+
await waitFor(element(by.id('single-feature-tests-scrollview'))).toBeVisible().withTimeout(3000);
38+
39+
await scrollUntilVisible(`single-feature-tests-${scenarioGroup}`, 'single-feature-tests-scrollview');
40+
await element(by.id(`single-feature-tests-${scenarioGroup}`)).tap();
41+
await waitFor(element(by.id(`${scenarioGroup}-scenarios-scrollview`))).toBeVisible().withTimeout(3000);
42+
43+
await scrollUntilVisible(`${screenKey}`, `${scenarioGroup}-scenarios-scrollview`,);
44+
await element(by.id(`${screenKey}`)).tap();
45+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { device, expect, element, by } from 'detox';
2+
import { selectSingleFeatureTestsScreen } from '../../e2e-utils';
3+
4+
describe('Tab Bar Hidden', () => {
5+
beforeAll(async () => {
6+
await device.reloadReactNative();
7+
await selectSingleFeatureTestsScreen('Tabs', 'test-tabs-tab-bar-hidden');
8+
});
9+
10+
it('Tab Bar Hidden screen should be displayed', async () => {
11+
await expect(element(by.id('tab-bar-hidden-switch'))).toBeVisible();
12+
await expect(element(by.id('tab-bar-hidden-scrollview'))).toBeVisible();
13+
});
14+
15+
it('Tab Bar should be visible by default after loading screen', async () => {
16+
await expect(element(by.label('tabBarHidden: false'))).toExist();
17+
// On iOS, we need to check for the whole tab bar visibility as view hierarchy shows individual tab bar items as exist and visible even when UITabBar is invisible. On Android, we can check for the individual tab bar item visibility as they are hidden together with the tab bar.
18+
if (device.getPlatform() === 'ios') {
19+
await expect(element(by.type('UITabBar'))).toBeVisible();
20+
} else {
21+
await expect(element(by.id('tab-bar-item-1-id'))).toBeVisible();
22+
}
23+
});
24+
25+
it('Tab Bar should be hidden after changing tabBarHidden value to true', async () => {
26+
await expect(element(by.id('tab-bar-hidden-switch'))).toHaveLabel('tabBarHidden: false');
27+
await element(by.id('tab-bar-hidden-switch')).tap();
28+
await expect(element(by.id('tab-bar-hidden-switch'))).toHaveLabel('tabBarHidden: true');
29+
if (device.getPlatform() === 'ios') {
30+
await expect(element(by.type('UITabBar'))).not.toBeVisible();
31+
} else {
32+
await expect(element(by.id('tab-bar-item-1-id'))).not.toBeVisible();
33+
}
34+
});
35+
36+
it('Tab Bar should reappear after changing tabBarHidden value to false', async () => {
37+
await expect(element(by.id('tab-bar-hidden-switch'))).toHaveLabel('tabBarHidden: true');
38+
await element(by.id('tab-bar-hidden-switch')).tap();
39+
await expect(element(by.id('tab-bar-hidden-switch'))).toHaveLabel('tabBarHidden: false');
40+
if (device.getPlatform() === 'ios') {
41+
await expect(element(by.type('UITabBar'))).toBeVisible();
42+
} else {
43+
await expect(element(by.id('tab-bar-item-1-id'))).toBeVisible();
44+
}
45+
});
46+
});

apps/src/tests/shared/ScenarioScreen.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import {
88
NavigationIndependentTree,
99
} from '@react-navigation/native';
1010

11-
function ScenarioSelect(props: { scenarios: Scenario[] }) {
11+
function ScenarioSelect(props: { scenarios: Scenario[]; groupName: string }) {
1212
return (
13-
<ScrollView contentInsetAdjustmentBehavior="automatic">
13+
<ScrollView contentInsetAdjustmentBehavior="automatic"
14+
testID={`${props.groupName}-scenarios-scrollview`}>
1415
{Object.values(props.scenarios).map(({ name, key, details, platforms }) => (
1516
<ScenarioButton
1617
title={name}
@@ -42,7 +43,7 @@ export default function ScenarioSelectionScreen(props: {
4243
headerLargeTitleEnabled: true,
4344
headerTitle: props.scenarioGroup.name,
4445
}}>
45-
{() => <ScenarioSelect scenarios={props.scenarioGroup.scenarios} />}
46+
{() => <ScenarioSelect scenarios={props.scenarioGroup.scenarios} groupName={props.scenarioGroup.name} />}
4647
</Stack.Screen>
4748
{Object.values(props.scenarioGroup.scenarios).map(({ key, AppComponent }) => (
4849
<Stack.Screen name={key} key={key} component={AppComponent} />

apps/src/tests/single-feature-tests/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ type ParamsList = { [k: keyof typeof COMPONENT_SCENARIOS]: undefined } & {
2626

2727
function HomeScreen() {
2828
return (
29-
<ScrollView contentInsetAdjustmentBehavior="automatic">
29+
<ScrollView contentInsetAdjustmentBehavior="automatic"
30+
testID="single-feature-tests-scrollview">
3031
{Object.entries(COMPONENT_SCENARIOS).map(([key, scenarioGroup]) => (
3132
<ScenarioButton
3233
key={key}
3334
title={scenarioGroup.name}
3435
route={key}
3536
details={scenarioGroup.details}
37+
testID={`single-feature-tests-${scenarioGroup.name}`}
3638
/>
3739
))}
3840
</ScrollView>

apps/src/tests/single-feature-tests/tabs/test-tabs-tab-bar-controller-mode-ios/scenario.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
## Prerequisites
66

7-
- iPhone simulator or device
8-
- iPad simulator or device (required for `tabSidebar` mode)
9-
- iOS 18+
7+
- iOS simulator or device: iPhone and iPad (required for `tabSidebar` mode).
108

119
## Steps
1210

@@ -73,4 +71,4 @@
7371

7472
14. Set tabBarControllerMode = `tabSidebar`
7573

76-
- [ ] Expected: Tab bar displayed at the **bottom**
74+
- [ ] Expected: Tab bar displayed at the **bottom**

apps/src/tests/single-feature-tests/tabs/test-tabs-tab-bar-hidden/index.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ function ConfigScreen() {
2222
const { hostConfig, updateHostConfig } = useTabsHostConfig();
2323

2424
return (
25-
<ScrollView style={{ padding: 40 }}>
26-
<Text style={{ textAlign: 'center' }}>Change flag value by clicking on button.</Text>
25+
<ScrollView style={{ padding: 40 }}
26+
testID="tab-bar-hidden-scrollview">
27+
<Text style={{ textAlign: 'center' }}>
28+
Change flag value by clicking on button.</Text>
2729
<SettingsSwitch
2830
style={{ marginTop: 20, marginBottom: 15 }}
2931
label="tabBarHidden"
3032
value={hostConfig.tabBarHidden ?? false}
3133
onValueChange={value => updateHostConfig({ tabBarHidden: value })}
34+
testID="tab-bar-hidden-switch"
3235
/>
3336
</ScrollView>
3437
);
@@ -40,6 +43,8 @@ const ROUTE_CONFIGS: TabRouteConfig[] = [
4043
Component: ConfigScreen,
4144
options: {
4245
...DEFAULT_TAB_ROUTE_OPTIONS,
46+
tabBarItemTestID: 'tab-bar-item-1-id',
47+
tabBarItemAccessibilityLabel: 'First Tab Item',
4348
title: 'Tab1',
4449
},
4550
},
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
# Test Scenario: tabBarHidden
22

3-
**E2E test:** No - to be automated
3+
**E2E test:** Yes, covers all manual scenario steps. For iOS test are covered only for iPhone, e2e is not suitable for iPad execution due to use 'UITabBar' type (on iPad new tab bar at the top is not an instance of UITabBar).
44

55
## Prerequisites
6-
- iOS 18+ device or simulator
6+
7+
- iOS device or simulator
78
- Android emulator
89

910
## Steps
1011

1112
1. Launch the app and navigate to the screen Tab Bar Hidden.
1213

1314
- [ ] Expected: Screen with one Tab in tab bar should be displayed.
15+
1416
2. Toggle `tabBarHidden` to `true`.
1517

1618
- [ ] Expected: Tab bar should disappear immediately.
19+
1720
3. Toggle back to `false`.
1821

1922
- [ ] Expected: Tab bar should reappear.

apps/src/tests/single-feature-tests/tabs/test-tabs-tab-bar-layout-direction/scenario.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
## Prerequisites
66

7-
- iOS 18+ device or simulator
7+
- iOS device or simulator
88
with at least one RTL language localization configured in Xcode (e.g. empty ar.lproj/InfoPlist.strings), or system language set to Arabic/Hebrew
99
- Android emulator
1010
Android emulator with supportRtl enabled in app manifest
@@ -80,4 +80,4 @@ Note:
8080

8181
12. System = LTR, forceRTL=true (restart), TabsHost = ltr
8282

83-
- [ ] Expected: Tab bar is LTR (TabsHost wins over forceRTL)
83+
- [ ] Expected: Tab bar is LTR (TabsHost wins over forceRTL)

0 commit comments

Comments
 (0)