From 2bb4393880fa5aed383ee1167aeaa1ffab2934bf Mon Sep 17 00:00:00 2001 From: lkuchno Date: Wed, 10 Jun 2026 16:49:59 +0200 Subject: [PATCH 1/5] chore(test): new e2e test for test-tabs-IME-insets --- .../tabs/test-tabs-ime-insets-android.e2e.ts | 133 ++++++++++++++++++ .../test-tabs-ime-insets-android/index.tsx | 7 +- .../scenario-description.ts | 2 +- 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts diff --git a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts new file mode 100644 index 0000000000..6fea61e0c6 --- /dev/null +++ b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts @@ -0,0 +1,133 @@ +import { expect as jestExpect } from '@jest/globals'; +import { device, expect, element, by } from 'detox'; +import { AndroidElementAttributes } from 'detox/detox'; +import { + describeIfAndroid, + selectSingleFeatureTestsScreen, +} from '../../e2e-utils'; + +async function getTabBarItemY(): Promise { + const attrs = (await element( + by.id('ime-insets-config-tab-item'), + ).getAttributes()) as AndroidElementAttributes; + return attrs.frame.y; +} +async function getTextY(): Promise { + const attrs = (await element( + by.id('tabs-screen-bottom-text'), + ).getAttributes()) as AndroidElementAttributes; + return attrs.frame.y; +} + +async function navigateToScreen() { + await device.reloadReactNative(); + await selectSingleFeatureTestsScreen('Tabs', 'test-tabs-ime-insets-android'); +} + +describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { + beforeEach(async () => { + await navigateToScreen(); + }); + + it('should display default switch states and bottom text on load', async () => { + await expect(element(by.id('safe-area-bottom-edge-switch'))).toBeVisible(); + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toBeVisible(); + await expect(element(by.id('ime-insets-text-input'))).toBeVisible(); + + await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( + 'safeAreaViewBottomEdgeEnabled: true', + ); + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: false'); + + await expect(element(by.id('tabs-screen-bottom-text'))).toBeVisible(); + }); + + it('tabBarRespectsIMEInsets: false — tab bar Y stays constant when keyboard opens', async () => { + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: false'); + + const yBefore = await getTabBarItemY(); + const yTextBefore = await getTabBarItemY(); + await element(by.id('ime-insets-text-input')).tap(); + const yAfter = await getTabBarItemY(); + const yTextAfter = await getTabBarItemY(); + + jestExpect(Math.abs(yAfter - yBefore)).toBeLessThan(5); + jestExpect(Math.abs(yTextAfter - yTextBefore)).toBeLessThan(5); + + await device.pressBack(); + }); + + it('tabBarRespectsIMEInsets: true — tab bar shifts above keyboard when keyboard opens', async () => { + await element(by.id('tab-bar-respects-ime-insets-switch')).tap(); + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: true'); + + const yBefore = await getTabBarItemY(); + const yTextBefore = await getTabBarItemY(); + await element(by.id('ime-insets-text-input')).tap(); + const yAfter = await getTabBarItemY(); + const yTextAfter = await getTabBarItemY(); + + jestExpect(yAfter).toBeLessThan(yBefore); + jestExpect(yTextAfter).toBeLessThan(yTextBefore); + + await device.pressBack(); + const yRestored = await getTabBarItemY(); + jestExpect(Math.abs(yRestored - yBefore)).toBeLessThan(5); + }); + + it('safeAreaViewBottomEdgeEnabled: false + tabBarRespectsIMEInsets: true — tab bar still shifts above keyboard', async () => { + await element(by.id('safe-area-bottom-edge-switch')).tap(); + await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( + 'safeAreaViewBottomEdgeEnabled: false', + ); + + await element(by.id('tab-bar-respects-ime-insets-switch')).tap(); + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: true'); + + const yBefore = await getTabBarItemY(); + const yText = await getTextY(); + jestExpect(yText).toBeGreaterThan(yBefore); + + await element(by.id('ime-insets-text-input')).tap(); + const yAfter = await getTabBarItemY(); + + jestExpect(yAfter).toBeLessThan(yBefore); + + await device.pressBack(); + }); + + it('both props false — tab bar stays at bottom when keyboard opens', async () => { + await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( + 'safeAreaViewBottomEdgeEnabled: true', + ); + await element(by.id('safe-area-bottom-edge-switch')).tap(); + await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( + 'safeAreaViewBottomEdgeEnabled: false', + ); + + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: false'); + + const yBefore = await getTabBarItemY(); + const yText = await getTextY(); + jestExpect(yText).toBeGreaterThan(yBefore); + + await element(by.id('ime-insets-text-input')).tap(); + const yAfter = await getTabBarItemY(); + + jestExpect(Math.abs(yAfter - yBefore)).toBeLessThan(5); + + await device.pressBack(); + }); +}); diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/index.tsx b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/index.tsx index dc0dabc98e..2be4f4c4dc 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/index.tsx +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/index.tsx @@ -39,6 +39,7 @@ function ConfigScreen() { onValueChange={function (value: boolean): void { setSafeAreaViewBottomEdgeEnabled(value); }} + testID="safe-area-bottom-edge-switch" /> @@ -49,6 +50,7 @@ function ConfigScreen() { onValueChange={function (value: boolean): void { updateHostConfig({ android: { tabBarRespectsIMEInsets: value } }); }} + testID="tab-bar-respects-ime-insets-switch" /> @@ -56,10 +58,11 @@ function ConfigScreen() { - TabsScreen bottom + TabsScreen bottom ); @@ -72,6 +75,7 @@ const ROUTE_CONFIGS: TabRouteConfig[] = [ options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'Config', + tabBarItemTestID: 'ime-insets-config-tab-item', safeAreaConfiguration: { edges: { bottom: true, @@ -85,6 +89,7 @@ const ROUTE_CONFIGS: TabRouteConfig[] = [ options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'Tab2', + tabBarItemTestID: 'ime-insets-tab2-tab-item', }, }, ]; diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts index 5283e267ea..f1321c0c56 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts @@ -6,6 +6,6 @@ export const scenarioDescription: ScenarioDescription = { details: 'Tests prop that determines whether BottomNavigationView respects IME insets.', platforms: ['android'], - e2eCoverage: 'tbd', + e2eCoverage: 'incomplete', smokeTest: false, }; From aa2655fa8b163bad3400d61878f53c5f6364c97d Mon Sep 17 00:00:00 2001 From: lkuchno Date: Thu, 11 Jun 2026 13:24:58 +0200 Subject: [PATCH 2/5] adding frame comparision between before and after y possition of elements and with open keyboard --- .../tabs/test-tabs-ime-insets-android.e2e.ts | 139 ++++++++++++------ .../scenario-description.ts | 2 +- .../test-tabs-ime-insets-android/scenario.md | 3 +- 3 files changed, 101 insertions(+), 43 deletions(-) diff --git a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts index 6fea61e0c6..6d7365294c 100644 --- a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts +++ b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts @@ -6,27 +6,51 @@ import { selectSingleFeatureTestsScreen, } from '../../e2e-utils'; -async function getTabBarItemY(): Promise { +const { + getCommandLineResponse, +} = require('../../../../scripts/e2e/command-line-helpers'); + +function readKeyboardTopAndroid(): number | null { + const dump = getCommandLineResponse( + `adb -s ${device.id} shell dumpsys window`, + ) as string; + const match = dump.match( + /type=ime frame=\[\d+,(\d+)\]\[\d+,\d+\][^\n]*\bvisible=true\b/, + ); + return match ? Number(match[1]) : null; +} + +async function getKeyboardTopAndroid(): Promise { + let last: number | null = null; + for (let i = 0; i < 20; i++) { + const top = readKeyboardTopAndroid(); + if (top !== null && top === last) return top; + last = top; + await new Promise(resolve => setTimeout(resolve, 100)); + } + throw new Error('Soft keyboard did not become visible on Android'); +} + +async function getTabBarItemAttrs(): Promise { const attrs = (await element( by.id('ime-insets-config-tab-item'), ).getAttributes()) as AndroidElementAttributes; - return attrs.frame.y; + return attrs; } -async function getTextY(): Promise { +async function getTextAttrs(): Promise { const attrs = (await element( by.id('tabs-screen-bottom-text'), ).getAttributes()) as AndroidElementAttributes; - return attrs.frame.y; -} - -async function navigateToScreen() { - await device.reloadReactNative(); - await selectSingleFeatureTestsScreen('Tabs', 'test-tabs-ime-insets-android'); + return attrs; } describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { - beforeEach(async () => { - await navigateToScreen(); + beforeAll(async () => { + await device.reloadReactNative(); + await selectSingleFeatureTestsScreen( + 'Tabs', + 'test-tabs-ime-insets-android', + ); }); it('should display default switch states and bottom text on load', async () => { @@ -51,14 +75,23 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { element(by.id('tab-bar-respects-ime-insets-switch')), ).toHaveLabel('tabBarRespectsIMEInsets: false'); - const yBefore = await getTabBarItemY(); - const yTextBefore = await getTabBarItemY(); + const yTabBefore = (await getTabBarItemAttrs()).frame.y; + const yTextBefore = (await getTextAttrs()).frame.y; + await element(by.id('ime-insets-text-input')).tap(); - const yAfter = await getTabBarItemY(); - const yTextAfter = await getTabBarItemY(); - jestExpect(Math.abs(yAfter - yBefore)).toBeLessThan(5); - jestExpect(Math.abs(yTextAfter - yTextBefore)).toBeLessThan(5); + const yTabAfter = (await getTabBarItemAttrs()).frame.y; + const yTextAfter = (await getTextAttrs()).frame.y; + const kbTop = await getKeyboardTopAndroid(); + + jestExpect(yTabAfter).toEqual(yTabBefore); + jestExpect(yTextAfter).toEqual(yTextBefore); + jestExpect( + yTabAfter + (await getTabBarItemAttrs()).frame.height, + ).toBeGreaterThan(kbTop); + jestExpect( + yTextAfter + (await getTextAttrs()).frame.height, + ).toBeGreaterThan(kbTop); await device.pressBack(); }); @@ -69,18 +102,30 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { element(by.id('tab-bar-respects-ime-insets-switch')), ).toHaveLabel('tabBarRespectsIMEInsets: true'); - const yBefore = await getTabBarItemY(); - const yTextBefore = await getTabBarItemY(); + const yTabBefore = (await getTabBarItemAttrs()).frame.y; + const yTextBefore = (await getTextAttrs()).frame.y; + await element(by.id('ime-insets-text-input')).tap(); - const yAfter = await getTabBarItemY(); - const yTextAfter = await getTabBarItemY(); - jestExpect(yAfter).toBeLessThan(yBefore); + const yTabAfter = (await getTabBarItemAttrs()).frame.y; + const yTextAfter = (await getTextAttrs()).frame.y; + const kbTop = await getKeyboardTopAndroid(); + + jestExpect(yTabAfter).toBeLessThan(yTabBefore); jestExpect(yTextAfter).toBeLessThan(yTextBefore); + jestExpect(yTextAfter).toBeLessThan(yTabAfter); + jestExpect( + yTabAfter + (await getTabBarItemAttrs()).frame.height, + ).toBeLessThanOrEqual(kbTop); + jestExpect( + yTextAfter + (await getTextAttrs()).frame.height, + ).toBeLessThanOrEqual(kbTop); await device.pressBack(); - const yRestored = await getTabBarItemY(); - jestExpect(Math.abs(yRestored - yBefore)).toBeLessThan(5); + + const yTabRestored = (await getTabBarItemAttrs()).frame.y; + + jestExpect(yTabRestored).toEqual(yTabBefore); }); it('safeAreaViewBottomEdgeEnabled: false + tabBarRespectsIMEInsets: true — tab bar still shifts above keyboard', async () => { @@ -88,45 +133,57 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( 'safeAreaViewBottomEdgeEnabled: false', ); - - await element(by.id('tab-bar-respects-ime-insets-switch')).tap(); await expect( element(by.id('tab-bar-respects-ime-insets-switch')), ).toHaveLabel('tabBarRespectsIMEInsets: true'); - const yBefore = await getTabBarItemY(); - const yText = await getTextY(); - jestExpect(yText).toBeGreaterThan(yBefore); + const yTabBefore = (await getTabBarItemAttrs()).frame.y; + const yTextBefore = (await getTextAttrs()).frame.y; + + jestExpect(yTextBefore).toBeGreaterThan(yTabBefore); await element(by.id('ime-insets-text-input')).tap(); - const yAfter = await getTabBarItemY(); - jestExpect(yAfter).toBeLessThan(yBefore); + const yTabAfter = (await getTabBarItemAttrs()).frame.y; + const yTextAfter = (await getTextAttrs()).frame.y; + const kbTop = await getKeyboardTopAndroid(); + + jestExpect( + yTabAfter + (await getTabBarItemAttrs()).frame.height, + ).toBeLessThanOrEqual(kbTop); + jestExpect(yTabAfter).toBeLessThan(yTabBefore); + jestExpect(yTextAfter).toEqual(yTextBefore); await device.pressBack(); }); it('both props false — tab bar stays at bottom when keyboard opens', async () => { - await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( - 'safeAreaViewBottomEdgeEnabled: true', - ); - await element(by.id('safe-area-bottom-edge-switch')).tap(); + await element(by.id('tab-bar-respects-ime-insets-switch')).tap(); await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( 'safeAreaViewBottomEdgeEnabled: false', ); - await expect( element(by.id('tab-bar-respects-ime-insets-switch')), ).toHaveLabel('tabBarRespectsIMEInsets: false'); - const yBefore = await getTabBarItemY(); - const yText = await getTextY(); - jestExpect(yText).toBeGreaterThan(yBefore); + const yTabBefore = (await getTabBarItemAttrs()).frame.y; + const yTextBefore = (await getTextAttrs()).frame.y; + jestExpect(yTextBefore).toBeGreaterThan(yTabBefore); await element(by.id('ime-insets-text-input')).tap(); - const yAfter = await getTabBarItemY(); - jestExpect(Math.abs(yAfter - yBefore)).toBeLessThan(5); + const yTabAfter = (await getTabBarItemAttrs()).frame.y; + const yTextAfter = (await getTextAttrs()).frame.y; + const kbTop = await getKeyboardTopAndroid(); + + jestExpect(yTabAfter).toEqual(yTabBefore); + jestExpect(yTextAfter).toEqual(yTextBefore); + jestExpect( + yTabAfter + (await getTabBarItemAttrs()).frame.height, + ).toBeGreaterThan(kbTop); + jestExpect( + yTextAfter + (await getTextAttrs()).frame.height, + ).toBeGreaterThan(kbTop); await device.pressBack(); }); diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts index f1321c0c56..d82ca9df99 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts @@ -6,6 +6,6 @@ export const scenarioDescription: ScenarioDescription = { details: 'Tests prop that determines whether BottomNavigationView respects IME insets.', platforms: ['android'], - e2eCoverage: 'incomplete', + e2eCoverage: 'full', smokeTest: false, }; diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md index f6bae73da6..722190e857 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md @@ -13,7 +13,8 @@ the layout content relative to the tab bar to prevent keyboard overlapping. ## E2E test -TBD: Planned, but will be implemented separately. +Full: All manual steps are covered by an E2E test based on changes to the tab bar +and text frame Y-positions,as well as their positions relative to the open keyboard. ## Prerequisites From c8d353d6d652a9f9c0bc761796c880e441bc5522 Mon Sep 17 00:00:00 2001 From: lkuchno Date: Thu, 11 Jun 2026 13:47:11 +0200 Subject: [PATCH 3/5] adding function to make sure that keyboard was hidden --- .../tabs/test-tabs-ime-insets-android.e2e.ts | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts index 6d7365294c..3ea9b127cc 100644 --- a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts +++ b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts @@ -22,13 +22,32 @@ function readKeyboardTopAndroid(): number | null { async function getKeyboardTopAndroid(): Promise { let last: number | null = null; - for (let i = 0; i < 20; i++) { + let stableCount = 0; + for (let i = 0; i < 40; i++) { const top = readKeyboardTopAndroid(); - if (top !== null && top === last) return top; + if (top !== null && top === last) { + stableCount++; + if (stableCount >= 2) return top; + } else { + stableCount = 0; + } last = top; await new Promise(resolve => setTimeout(resolve, 100)); } - throw new Error('Soft keyboard did not become visible on Android'); + throw new Error( + `Soft keyboard did not stabilize on Android (last detected top: ${last})`, + ); +} + +async function dismissKeyboardAndroid(): Promise { + await device.pressBack(); + for (let i = 0; i < 40; i++) { + if (readKeyboardTopAndroid() === null) return; + await new Promise(resolve => setTimeout(resolve, 100)); + } + throw new Error( + `Soft keyboard still visible on Android after pressBack (top: ${readKeyboardTopAndroid()})`, + ); } async function getTabBarItemAttrs(): Promise { @@ -80,9 +99,9 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { await element(by.id('ime-insets-text-input')).tap(); + const kbTop = await getKeyboardTopAndroid(); const yTabAfter = (await getTabBarItemAttrs()).frame.y; const yTextAfter = (await getTextAttrs()).frame.y; - const kbTop = await getKeyboardTopAndroid(); jestExpect(yTabAfter).toEqual(yTabBefore); jestExpect(yTextAfter).toEqual(yTextBefore); @@ -93,7 +112,7 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { yTextAfter + (await getTextAttrs()).frame.height, ).toBeGreaterThan(kbTop); - await device.pressBack(); + await dismissKeyboardAndroid(); }); it('tabBarRespectsIMEInsets: true — tab bar shifts above keyboard when keyboard opens', async () => { @@ -107,9 +126,9 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { await element(by.id('ime-insets-text-input')).tap(); + const kbTop = await getKeyboardTopAndroid(); const yTabAfter = (await getTabBarItemAttrs()).frame.y; const yTextAfter = (await getTextAttrs()).frame.y; - const kbTop = await getKeyboardTopAndroid(); jestExpect(yTabAfter).toBeLessThan(yTabBefore); jestExpect(yTextAfter).toBeLessThan(yTextBefore); @@ -121,7 +140,7 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { yTextAfter + (await getTextAttrs()).frame.height, ).toBeLessThanOrEqual(kbTop); - await device.pressBack(); + await dismissKeyboardAndroid(); const yTabRestored = (await getTabBarItemAttrs()).frame.y; @@ -144,9 +163,9 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { await element(by.id('ime-insets-text-input')).tap(); + const kbTop = await getKeyboardTopAndroid(); const yTabAfter = (await getTabBarItemAttrs()).frame.y; const yTextAfter = (await getTextAttrs()).frame.y; - const kbTop = await getKeyboardTopAndroid(); jestExpect( yTabAfter + (await getTabBarItemAttrs()).frame.height, @@ -154,7 +173,7 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { jestExpect(yTabAfter).toBeLessThan(yTabBefore); jestExpect(yTextAfter).toEqual(yTextBefore); - await device.pressBack(); + await dismissKeyboardAndroid(); }); it('both props false — tab bar stays at bottom when keyboard opens', async () => { @@ -172,9 +191,9 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { await element(by.id('ime-insets-text-input')).tap(); + const kbTop = await getKeyboardTopAndroid(); const yTabAfter = (await getTabBarItemAttrs()).frame.y; const yTextAfter = (await getTextAttrs()).frame.y; - const kbTop = await getKeyboardTopAndroid(); jestExpect(yTabAfter).toEqual(yTabBefore); jestExpect(yTextAfter).toEqual(yTextBefore); @@ -185,6 +204,6 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { yTextAfter + (await getTextAttrs()).frame.height, ).toBeGreaterThan(kbTop); - await device.pressBack(); + await dismissKeyboardAndroid(); }); }); From 0e9db1598ef09fd828853063d13d21e35eb770cc Mon Sep 17 00:00:00 2001 From: lkuchno Date: Thu, 11 Jun 2026 14:34:48 +0200 Subject: [PATCH 4/5] removing checks with keyboard height --- .../tabs/test-tabs-ime-insets-android.e2e.ts | 81 ++----------------- 1 file changed, 6 insertions(+), 75 deletions(-) diff --git a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts index 3ea9b127cc..132a916572 100644 --- a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts +++ b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts @@ -6,50 +6,6 @@ import { selectSingleFeatureTestsScreen, } from '../../e2e-utils'; -const { - getCommandLineResponse, -} = require('../../../../scripts/e2e/command-line-helpers'); - -function readKeyboardTopAndroid(): number | null { - const dump = getCommandLineResponse( - `adb -s ${device.id} shell dumpsys window`, - ) as string; - const match = dump.match( - /type=ime frame=\[\d+,(\d+)\]\[\d+,\d+\][^\n]*\bvisible=true\b/, - ); - return match ? Number(match[1]) : null; -} - -async function getKeyboardTopAndroid(): Promise { - let last: number | null = null; - let stableCount = 0; - for (let i = 0; i < 40; i++) { - const top = readKeyboardTopAndroid(); - if (top !== null && top === last) { - stableCount++; - if (stableCount >= 2) return top; - } else { - stableCount = 0; - } - last = top; - await new Promise(resolve => setTimeout(resolve, 100)); - } - throw new Error( - `Soft keyboard did not stabilize on Android (last detected top: ${last})`, - ); -} - -async function dismissKeyboardAndroid(): Promise { - await device.pressBack(); - for (let i = 0; i < 40; i++) { - if (readKeyboardTopAndroid() === null) return; - await new Promise(resolve => setTimeout(resolve, 100)); - } - throw new Error( - `Soft keyboard still visible on Android after pressBack (top: ${readKeyboardTopAndroid()})`, - ); -} - async function getTabBarItemAttrs(): Promise { const attrs = (await element( by.id('ime-insets-config-tab-item'), @@ -99,20 +55,13 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { await element(by.id('ime-insets-text-input')).tap(); - const kbTop = await getKeyboardTopAndroid(); const yTabAfter = (await getTabBarItemAttrs()).frame.y; const yTextAfter = (await getTextAttrs()).frame.y; jestExpect(yTabAfter).toEqual(yTabBefore); jestExpect(yTextAfter).toEqual(yTextBefore); - jestExpect( - yTabAfter + (await getTabBarItemAttrs()).frame.height, - ).toBeGreaterThan(kbTop); - jestExpect( - yTextAfter + (await getTextAttrs()).frame.height, - ).toBeGreaterThan(kbTop); - - await dismissKeyboardAndroid(); + + await device.pressBack(); }); it('tabBarRespectsIMEInsets: true — tab bar shifts above keyboard when keyboard opens', async () => { @@ -126,21 +75,14 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { await element(by.id('ime-insets-text-input')).tap(); - const kbTop = await getKeyboardTopAndroid(); const yTabAfter = (await getTabBarItemAttrs()).frame.y; const yTextAfter = (await getTextAttrs()).frame.y; jestExpect(yTabAfter).toBeLessThan(yTabBefore); jestExpect(yTextAfter).toBeLessThan(yTextBefore); jestExpect(yTextAfter).toBeLessThan(yTabAfter); - jestExpect( - yTabAfter + (await getTabBarItemAttrs()).frame.height, - ).toBeLessThanOrEqual(kbTop); - jestExpect( - yTextAfter + (await getTextAttrs()).frame.height, - ).toBeLessThanOrEqual(kbTop); - await dismissKeyboardAndroid(); + await device.pressBack(); const yTabRestored = (await getTabBarItemAttrs()).frame.y; @@ -163,17 +105,13 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { await element(by.id('ime-insets-text-input')).tap(); - const kbTop = await getKeyboardTopAndroid(); const yTabAfter = (await getTabBarItemAttrs()).frame.y; const yTextAfter = (await getTextAttrs()).frame.y; - jestExpect( - yTabAfter + (await getTabBarItemAttrs()).frame.height, - ).toBeLessThanOrEqual(kbTop); jestExpect(yTabAfter).toBeLessThan(yTabBefore); jestExpect(yTextAfter).toEqual(yTextBefore); - await dismissKeyboardAndroid(); + await device.pressBack(); }); it('both props false — tab bar stays at bottom when keyboard opens', async () => { @@ -191,19 +129,12 @@ describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { await element(by.id('ime-insets-text-input')).tap(); - const kbTop = await getKeyboardTopAndroid(); const yTabAfter = (await getTabBarItemAttrs()).frame.y; const yTextAfter = (await getTextAttrs()).frame.y; jestExpect(yTabAfter).toEqual(yTabBefore); jestExpect(yTextAfter).toEqual(yTextBefore); - jestExpect( - yTabAfter + (await getTabBarItemAttrs()).frame.height, - ).toBeGreaterThan(kbTop); - jestExpect( - yTextAfter + (await getTextAttrs()).frame.height, - ).toBeGreaterThan(kbTop); - - await dismissKeyboardAndroid(); + + await device.pressBack(); }); }); From cd9781fa586569a63f5dd34a6e2c801d9a53492a Mon Sep 17 00:00:00 2001 From: lkuchno Date: Thu, 11 Jun 2026 14:36:01 +0200 Subject: [PATCH 5/5] e2e test section updated in scenario.md --- .../tabs/test-tabs-ime-insets-android/scenario.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md index 722190e857..36e9702975 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md @@ -14,7 +14,7 @@ the layout content relative to the tab bar to prevent keyboard overlapping. ## E2E test Full: All manual steps are covered by an E2E test based on changes to the tab bar -and text frame Y-positions,as well as their positions relative to the open keyboard. +and text frame Y-positions. ## Prerequisites