From 73c25abfa525ad1e2075c934ebc951aa5ef5f9a1 Mon Sep 17 00:00:00 2001 From: lkuchno Date: Wed, 29 Apr 2026 15:17:37 +0200 Subject: [PATCH 1/6] adding new functions - describeIfAndroid, tapSelectedTab --- FabricExample/e2e/e2e-utils.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/FabricExample/e2e/e2e-utils.ts b/FabricExample/e2e/e2e-utils.ts index 21e9d28657..83cb38e60f 100644 --- a/FabricExample/e2e/e2e-utils.ts +++ b/FabricExample/e2e/e2e-utils.ts @@ -1,8 +1,12 @@ import { device, expect, element, by } from 'detox'; +import { AndroidElementAttributes, IosElementAttributes } from 'detox/detox'; export const describeIfiOS = device.getPlatform() === 'ios' ? describe : describe.skip; +export const describeIfAndroid = + device.getPlatform() === 'android' ? describe : describe.skip; + async function scrollUntilVisible(id: string, scrollViewId: string) { await waitFor(element(by.id(id))) .toBeVisible() @@ -62,3 +66,24 @@ export async function selectSingleFeatureTestsScreen( ); await element(by.id(`${screenKey}`)).tap(); } +type ElementAttributes = IosElementAttributes | AndroidElementAttributes; + +export async function getElementAttributes( + testLabel: string, +): Promise { + const attrs = await element(by.label(testLabel)).getAttributes(); + return attrs as ElementAttributes; +} + +export async function tapSelectedTab(testLabel: string) { + if (device.getPlatform() === 'ios') { + const elementAttributes = await getElementAttributes(testLabel); + const { x, y, width, height } = elementAttributes.frame; + await device.tap({ + x: x + width / 2, + y: y + height / 2, + }); + } else { + await element(by.label(testLabel)).tap(); + } +} From a8e06423bc6721589f3c9f6491166322995d250d Mon Sep 17 00:00:00 2001 From: lkuchno Date: Wed, 29 Apr 2026 15:18:42 +0200 Subject: [PATCH 2/6] new e2e test and changes for it in scenario and index file --- ...-tabs-special-effects-scroll-to-top.e2e.ts | 79 +++++++++++++++++++ .../index.tsx | 26 ++++-- .../scenario.md | 4 +- 3 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts diff --git a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts new file mode 100644 index 0000000000..3794726e0c --- /dev/null +++ b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts @@ -0,0 +1,79 @@ +import { device, expect, element, by } from 'detox'; +import { + selectSingleFeatureTestsScreen, + tapSelectedTab, +} from '../../e2e-utils'; + +describe('Tabs specialEffects — scrollToTop', () => { + beforeAll(async () => { + await device.reloadReactNative(); + await selectSingleFeatureTestsScreen( + 'Tabs', + 'test-tabs-special-effects-scroll-to-top', + ); + }); + + it('should display tab bar and Tab1 scrollable list on load', async () => { + await expect(element(by.id('tab1-scrollview'))).toBeVisible(); + await expect(element(by.id('tab1-item-1'))).toBeVisible(); + if (device.getPlatform() === 'ios') { + await expect(element(by.type('UITabBar'))).toBeVisible(); + } else { + await expect(element(by.id('tab1-tab-item'))).toBeVisible(); + await expect(element(by.id('tab2-tab-item'))).toBeVisible(); + await expect(element(by.id('tab3-tab-item'))).toBeVisible(); + } + }); + + it('Tab1 (scrollToTop: true) — re-tapping active tab scrolls list back to top', async () => { + await element(by.id('tab1-scrollview')).scroll(300, 'down', NaN, 0.85); + await expect(element(by.id('tab1-item-1'))).not.toBeVisible(); + + await tapSelectedTab('tab1-tab-item-label'); + + await waitFor(element(by.id('tab1-item-1'))) + .toBeVisible() + .withTimeout(3000); + }); + + it('Tab2 (scrollToTop: false) — re-tapping active tab preserves scroll position', async () => { + await element(by.id('tab2-tab-item')).tap(); + await expect(element(by.id('tab2-item-1'))).toBeVisible(); + + await element(by.id('tab2-scrollview')).scroll(300, 'down', NaN, 0.85); + await expect(element(by.id('tab2-item-1'))).not.toBeVisible(); + + await tapSelectedTab('tab2-tab-item-label'); + + await expect(element(by.id('tab2-item-1'))).not.toBeVisible(); + }); + + it('Tab3 (no specialEffects) — re-tapping active tab scrolls list back to top', async () => { + await element(by.id('tab3-tab-item')).tap(); + await expect(element(by.id('tab3-item-1'))).toBeVisible(); + + await element(by.id('tab3-scrollview')).scroll(300, 'down', NaN, 0.85); + await expect(element(by.id('tab3-item-1'))).not.toBeVisible(); + + await tapSelectedTab('tab3-tab-item-label'); + + await waitFor(element(by.id('tab3-item-1'))) + .toBeVisible() + .withTimeout(3000); + }); + + it('Tab1 (scrollToTop: true) — switching away and back preserves scroll position', async () => { + await tapSelectedTab('tab1-tab-item-label'); + await expect(element(by.id('tab1-item-1'))).toBeVisible(); + + await element(by.id('tab1-scrollview')).scroll(300, 'down', NaN, 0.85); + await expect(element(by.id('tab1-item-1'))).not.toBeVisible(); + + await tapSelectedTab('tab3-tab-item-label'); + await expect(element(by.id('tab3-item-1'))).toBeVisible(); + + await tapSelectedTab('tab1-tab-item-label'); + + await expect(element(by.id('tab1-item-1'))).not.toBeVisible(); + }); +}); diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top/index.tsx b/apps/src/tests/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top/index.tsx index b46cfc5315..99b01e4cd1 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top/index.tsx +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top/index.tsx @@ -9,19 +9,23 @@ import { } from '@apps/shared/gamma/containers/tabs'; const scenarioDescription: ScenarioDescription = { - name: 'Tabs scrollToTop special effect', + name: 'Tabs special effect scroll to top', key: 'test-tabs-special-effects-scroll-to-top', details: - 'Test settings of scrollToTop specialEffect.', + 'Test settings of specialEffect scrollToTop.', platforms: ['ios', 'android'], }; -export function ScrollScreen() { +interface ScrollScreenProps { + tabName: string; +} + +export function ScrollScreen({ tabName }: ScrollScreenProps) { return ( - + Scroll Screen — scroll down or re-tap the tab. {Array.from({ length: 50 }, (_, i) => ( - + Item {i + 1} ))} @@ -32,10 +36,12 @@ export function ScrollScreen() { const TAB_CONFIGS: TabRouteConfig[] = [ { name: 'Tab1', - Component: ScrollScreen, + Component: () => , options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'Tab1', + tabBarItemTestID: 'tab1-tab-item', + tabBarItemAccessibilityLabel: 'tab1-tab-item-label', specialEffects: { repeatedTabSelection: { scrollToTop: true, @@ -45,10 +51,12 @@ const TAB_CONFIGS: TabRouteConfig[] = [ }, { name: 'Tab2', - Component: ScrollScreen, + Component: () => , options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'Tab2', + tabBarItemTestID: 'tab2-tab-item', + tabBarItemAccessibilityLabel: 'tab2-tab-item-label', specialEffects: { repeatedTabSelection: { scrollToTop: false @@ -58,10 +66,12 @@ const TAB_CONFIGS: TabRouteConfig[] = [ }, { name: 'Tab3', - Component: ScrollScreen, + Component: () => , options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'Tab3', + tabBarItemTestID: 'tab3-tab-item', + tabBarItemAccessibilityLabel: 'tab3-tab-item-label', }, }, ]; diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top/scenario.md b/apps/src/tests/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top/scenario.md index 64399651fd..41767cf2f1 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top/scenario.md +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top/scenario.md @@ -11,7 +11,7 @@ that there is no scroll-to-top behavior when it is disabled or absent. ## E2E test -Other: ongoing research. +Yes: Covers all manual scenario steps. ## Prerequisites @@ -28,7 +28,7 @@ list of 50 items. ### scrollToTop: true -1. Launch the app and navigate to the screen **Tabs specialEffects**. +1. Launch the app and navigate to the screen **Tabs special effect scroll to top**. - [ ] Expected: Three tabs (Tab1, Tab2, Tab3) are visible in the tab bar. Tab1 is active and displays a scrollable list of items. From 5df55821739089fef19d4b7cd4cfee5ba58686fa Mon Sep 17 00:00:00 2001 From: lkuchno <45803783+LKuchno@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:49:23 +0200 Subject: [PATCH 3/6] Update FabricExample/e2e/e2e-utils.ts Co-authored-by: Krzysztof Ligarski <63918941+kligarski@users.noreply.github.com> --- FabricExample/e2e/e2e-utils.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/FabricExample/e2e/e2e-utils.ts b/FabricExample/e2e/e2e-utils.ts index 83cb38e60f..251d829336 100644 --- a/FabricExample/e2e/e2e-utils.ts +++ b/FabricExample/e2e/e2e-utils.ts @@ -72,6 +72,13 @@ export async function getElementAttributes( testLabel: string, ): Promise { const attrs = await element(by.label(testLabel)).getAttributes(); + + if ('elements' in attrs) { + throw new Error( + `Multiple elements (${attrs.elements.length}) found for label: "${testLabel}". ` + ); + } + return attrs as ElementAttributes; } From fdc7b0af6374b567a34c00ae4589d6a406d35f51 Mon Sep 17 00:00:00 2001 From: lkuchno Date: Thu, 30 Apr 2026 10:10:01 +0200 Subject: [PATCH 4/6] change function in e2e-utils file and add platform handler to test --- FabricExample/e2e/e2e-utils.ts | 18 ++++++--------- ...-tabs-special-effects-scroll-to-top.e2e.ts | 22 +++++++++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/FabricExample/e2e/e2e-utils.ts b/FabricExample/e2e/e2e-utils.ts index 83cb38e60f..5cb8566be9 100644 --- a/FabricExample/e2e/e2e-utils.ts +++ b/FabricExample/e2e/e2e-utils.ts @@ -75,15 +75,11 @@ export async function getElementAttributes( return attrs as ElementAttributes; } -export async function tapSelectedTab(testLabel: string) { - if (device.getPlatform() === 'ios') { - const elementAttributes = await getElementAttributes(testLabel); - const { x, y, width, height } = elementAttributes.frame; - await device.tap({ - x: x + width / 2, - y: y + height / 2, - }); - } else { - await element(by.label(testLabel)).tap(); - } +export async function forceTapByLabeliOS(testLabel: string) { + const elementAttributes = await getElementAttributes(testLabel); + const { x, y, width, height } = elementAttributes.frame; + await device.tap({ + x: x + width / 2, + y: y + height / 2, + }); } diff --git a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts index 3794726e0c..86dc7109e3 100644 --- a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts +++ b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts @@ -1,9 +1,17 @@ import { device, expect, element, by } from 'detox'; import { selectSingleFeatureTestsScreen, - tapSelectedTab, + forceTapByLabeliOS, } from '../../e2e-utils'; +async function forceSelectTabByLabel(label: string) { + if (device.getPlatform() === 'ios') { + await forceTapByLabeliOS(label); + } else { + await element(by.label(label)).tap(); + } +} + describe('Tabs specialEffects — scrollToTop', () => { beforeAll(async () => { await device.reloadReactNative(); @@ -29,7 +37,7 @@ describe('Tabs specialEffects — scrollToTop', () => { await element(by.id('tab1-scrollview')).scroll(300, 'down', NaN, 0.85); await expect(element(by.id('tab1-item-1'))).not.toBeVisible(); - await tapSelectedTab('tab1-tab-item-label'); + await forceSelectTabByLabel('tab1-tab-item-label'); await waitFor(element(by.id('tab1-item-1'))) .toBeVisible() @@ -43,7 +51,7 @@ describe('Tabs specialEffects — scrollToTop', () => { await element(by.id('tab2-scrollview')).scroll(300, 'down', NaN, 0.85); await expect(element(by.id('tab2-item-1'))).not.toBeVisible(); - await tapSelectedTab('tab2-tab-item-label'); + await forceSelectTabByLabel('tab2-tab-item-label'); await expect(element(by.id('tab2-item-1'))).not.toBeVisible(); }); @@ -55,7 +63,7 @@ describe('Tabs specialEffects — scrollToTop', () => { await element(by.id('tab3-scrollview')).scroll(300, 'down', NaN, 0.85); await expect(element(by.id('tab3-item-1'))).not.toBeVisible(); - await tapSelectedTab('tab3-tab-item-label'); + await forceSelectTabByLabel('tab3-tab-item-label'); await waitFor(element(by.id('tab3-item-1'))) .toBeVisible() @@ -63,16 +71,16 @@ describe('Tabs specialEffects — scrollToTop', () => { }); it('Tab1 (scrollToTop: true) — switching away and back preserves scroll position', async () => { - await tapSelectedTab('tab1-tab-item-label'); + await forceSelectTabByLabel('tab1-tab-item-label'); await expect(element(by.id('tab1-item-1'))).toBeVisible(); await element(by.id('tab1-scrollview')).scroll(300, 'down', NaN, 0.85); await expect(element(by.id('tab1-item-1'))).not.toBeVisible(); - await tapSelectedTab('tab3-tab-item-label'); + await forceSelectTabByLabel('tab3-tab-item-label'); await expect(element(by.id('tab3-item-1'))).toBeVisible(); - await tapSelectedTab('tab1-tab-item-label'); + await forceSelectTabByLabel('tab1-tab-item-label'); await expect(element(by.id('tab1-item-1'))).not.toBeVisible(); }); From 0beb695d350876c45772682f372286a9917a18b6 Mon Sep 17 00:00:00 2001 From: lkuchno <45803783+LKuchno@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:22:25 +0200 Subject: [PATCH 5/6] Update FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts Co-authored-by: Krzysztof Ligarski <63918941+kligarski@users.noreply.github.com> --- .../tabs/test-tabs-special-effects-scroll-to-top.e2e.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts index 86dc7109e3..ce268e7e00 100644 --- a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts +++ b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-special-effects-scroll-to-top.e2e.ts @@ -4,6 +4,10 @@ import { forceTapByLabeliOS, } from '../../e2e-utils'; +/** + * Selects a tab bar item. On iOS, this uses a forced coordinate tap to + * ensure the tab is selected even if it is obstructed by the iOS 26 Liquid Glass lens. + */ async function forceSelectTabByLabel(label: string) { if (device.getPlatform() === 'ios') { await forceTapByLabeliOS(label); From d732793c9e86b95dd219719116aa85cdf06a257e Mon Sep 17 00:00:00 2001 From: lkuchno <45803783+LKuchno@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:22:50 +0200 Subject: [PATCH 6/6] Update FabricExample/e2e/e2e-utils.ts Co-authored-by: Krzysztof Ligarski <63918941+kligarski@users.noreply.github.com> --- FabricExample/e2e/e2e-utils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FabricExample/e2e/e2e-utils.ts b/FabricExample/e2e/e2e-utils.ts index a406cffe07..65d38c04e9 100644 --- a/FabricExample/e2e/e2e-utils.ts +++ b/FabricExample/e2e/e2e-utils.ts @@ -82,6 +82,10 @@ export async function getElementAttributes( return attrs as ElementAttributes; } +/** + * Performs a coordinate-based tap on iOS to interact with an element that may be + * obstructed by other UI layers, bypassing Detox's default visibility checks. + */ export async function forceTapByLabeliOS(testLabel: string) { const elementAttributes = await getElementAttributes(testLabel); const { x, y, width, height } = elementAttributes.frame;