From 11881c4f538473e46abdc1d0849572ab99aee2d6 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 25 Dec 2024 15:06:50 +0100 Subject: [PATCH 1/5] wip add test --- src/libs/E2E/reactNativeLaunchingTest.ts | 1 + src/pages/home/ReportScreen.tsx | 12 +++++++++++- tests/e2e/config.ts | 8 ++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libs/E2E/reactNativeLaunchingTest.ts b/src/libs/E2E/reactNativeLaunchingTest.ts index fdd305baf88c..f1b392385994 100644 --- a/src/libs/E2E/reactNativeLaunchingTest.ts +++ b/src/libs/E2E/reactNativeLaunchingTest.ts @@ -36,6 +36,7 @@ const tests: Tests = { [E2EConfig.TEST_NAMES.ChatOpening]: require('./tests/chatOpeningTest.e2e').default, [E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default, [E2EConfig.TEST_NAMES.Linking]: require('./tests/linkingTest.e2e').default, + [E2EConfig.TEST_NAMES.ChatRerenering]: require('./tests/chatRerenderingTest.e2e').default, }; // Once we receive the TII measurement we know that the app is initialized and ready to be used: diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 6b1b66aa6138..e7a697c8f601 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -34,6 +34,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import clearReportNotifications from '@libs/Notification/clearReportNotifications'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import Performance from '@libs/Performance'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -289,6 +290,11 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro const isTopMostReportId = currentReportID === reportIDFromRoute; const didSubscribeToReportLeavingEvents = useRef(false); + const renderCount = useRef(0); + useEffect(() => { + renderCount.current += 1; + console.log(`+++++++++++ Unique rerenders: SCREEN #${renderCount.current}`); + }); useEffect(() => { if (!report?.reportID || shouldHideReport) { wasReportAccessibleRef.current = false; @@ -878,4 +884,8 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro } ReportScreen.displayName = 'ReportScreen'; -export default withCurrentReportID(memo(ReportScreen, (prevProps, nextProps) => prevProps.currentReportID === nextProps.currentReportID && lodashIsEqual(prevProps.route, nextProps.route))); +const MemoizedReportScreen = memo(ReportScreen, (prevProps, nextProps) => prevProps.currentReportID === nextProps.currentReportID && lodashIsEqual(prevProps.route, nextProps.route)); +const WithCurrentReportID = withCurrentReportID(MemoizedReportScreen); + +export default Performance.withRenderTrace({id: ' rendering'})(WithCurrentReportID); +export type {ReportScreenProps, ReportScreenNavigationProps}; diff --git a/tests/e2e/config.ts b/tests/e2e/config.ts index c8e89721c998..30836a79971b 100644 --- a/tests/e2e/config.ts +++ b/tests/e2e/config.ts @@ -8,6 +8,7 @@ const TEST_NAMES = { ReportTyping: 'Report typing', ChatOpening: 'Chat opening', Linking: 'Linking', + ChatRerenering: 'Chat rerendering', }; /** @@ -100,6 +101,13 @@ export default { linkedReportID: '5421294415618529', linkedReportActionID: '2845024374735019929', }, + [TEST_NAMES.ChatRerenering]: { + name: TEST_NAMES.ChatRerenering, + reportScreen: { + autoFocus: false, + }, + reportID: '8268282951170052', + }, }, }; From f874ac204d88d1609d6c4df38437224e31b976d7 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 25 Dec 2024 15:30:49 +0100 Subject: [PATCH 2/5] rerendering test --- src/libs/E2E/tests/chatRerenderingTest.e2e.ts | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/libs/E2E/tests/chatRerenderingTest.e2e.ts diff --git a/src/libs/E2E/tests/chatRerenderingTest.e2e.ts b/src/libs/E2E/tests/chatRerenderingTest.e2e.ts new file mode 100644 index 000000000000..848b802f0467 --- /dev/null +++ b/src/libs/E2E/tests/chatRerenderingTest.e2e.ts @@ -0,0 +1,115 @@ +import Config from 'react-native-config'; +import type {NativeConfig} from 'react-native-config'; +import E2ELogin from '@libs/E2E/actions/e2eLogin'; +import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; +import E2EClient from '@libs/E2E/client'; +import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; +import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; +import Navigation from '@libs/Navigation/Navigation'; +import Performance from '@libs/Performance'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +const RERENDER_WAIT_TIME = 5000; +const RENDER_DEBOUNCE_TIME = 0; // Add debounce time to group rapid rerenders + +const test = (config: NativeConfig) => { + console.debug('=========================================='); + console.debug('[E2E] Starting rerender test'); + console.debug('=========================================='); + + const reportID = getConfigValueOrThrow('reportID', config); + const name = getConfigValueOrThrow('name', config); + + // Track both raw and unique render times + const renderTimes = new Set(); + const rawRenderTimes: number[] = []; + const startTestTime = Date.now(); + let uniqueRenderCount = 0; + let totalRenderCount = 0; + + // Add render phase tracking + const renderPhases = { + mount: 0, + update: 0, + 'nested-update': 0, + }; + + const logStats = () => { + console.debug('=========================================='); + console.debug('[E2E] RERENDER TEST STATS'); + console.debug(`Total renders: ${totalRenderCount}`); + console.debug(`Unique renders (${RENDER_DEBOUNCE_TIME}ms debounce): ${uniqueRenderCount}`); + console.debug('Render phases:', renderPhases); + console.debug(`Total duration: ${Date.now() - startTestTime}ms`); + console.debug('Render times (debounced):', Array.from(renderTimes)); + console.debug('Raw render times:', rawRenderTimes); + console.debug('=========================================='); + }; + + E2ELogin().then((neededLogin) => { + if (neededLogin) { + return waitForAppLoaded().then(() => E2EClient.submitTestDone()); + } + + const [openReportPromise, openReportResolve] = getPromiseWithResolve(); + + openReportPromise + .then(() => { + logStats(); + console.debug('[E2E] Test completed'); + E2EClient.submitTestDone(); + }) + .catch((err) => { + console.debug('[E2E] Error:', err); + console.error(err); + }); + + Performance.subscribeToMeasurements((entry) => { + if (entry.name === ' rendering') { + const currentTime = Date.now(); + const phase = entry.detail?.phase; + + // Track all renders + if (['mount', 'update', 'nested-update'].includes(phase)) { + totalRenderCount++; + rawRenderTimes.push(currentTime); + + // Increment phase counter + if (phase) { + renderPhases[phase]++; + } + + // Check if this render is unique (debounced) + const isUniqueRender = !Array.from(renderTimes).some((time) => Math.abs(currentTime - time) < RENDER_DEBOUNCE_TIME); + + if (isUniqueRender) { + renderTimes.add(currentTime); + uniqueRenderCount++; + } + } + } + + if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { + console.debug('[E2E] Sidebar loaded -> Navigating to report'); + Performance.markStart(CONST.TIMING.OPEN_REPORT); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); + } + + if (entry.name === CONST.TIMING.OPEN_REPORT) { + setTimeout(() => { + logStats(); + E2EClient.submitTestResults({ + branch: Config.E2E_BRANCH, + name, + metric: uniqueRenderCount, + unit: 'renders', + }); + openReportResolve(); + }, RERENDER_WAIT_TIME); + } + }); + }); +}; + +export default test; From 48890a64dd66a046fa848608878a814b93bfba3d Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 27 Dec 2024 16:54:41 +0100 Subject: [PATCH 3/5] update test --- src/libs/E2E/tests/chatRerenderingTest.e2e.ts | 89 +++++++------------ 1 file changed, 31 insertions(+), 58 deletions(-) diff --git a/src/libs/E2E/tests/chatRerenderingTest.e2e.ts b/src/libs/E2E/tests/chatRerenderingTest.e2e.ts index 848b802f0467..89dc53ff6186 100644 --- a/src/libs/E2E/tests/chatRerenderingTest.e2e.ts +++ b/src/libs/E2E/tests/chatRerenderingTest.e2e.ts @@ -10,8 +10,7 @@ import Performance from '@libs/Performance'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -const RERENDER_WAIT_TIME = 5000; -const RENDER_DEBOUNCE_TIME = 0; // Add debounce time to group rapid rerenders +const RERENDER_WAIT_TIME = 7000; const test = (config: NativeConfig) => { console.debug('=========================================='); @@ -21,31 +20,8 @@ const test = (config: NativeConfig) => { const reportID = getConfigValueOrThrow('reportID', config); const name = getConfigValueOrThrow('name', config); - // Track both raw and unique render times - const renderTimes = new Set(); - const rawRenderTimes: number[] = []; - const startTestTime = Date.now(); - let uniqueRenderCount = 0; let totalRenderCount = 0; - - // Add render phase tracking - const renderPhases = { - mount: 0, - update: 0, - 'nested-update': 0, - }; - - const logStats = () => { - console.debug('=========================================='); - console.debug('[E2E] RERENDER TEST STATS'); - console.debug(`Total renders: ${totalRenderCount}`); - console.debug(`Unique renders (${RENDER_DEBOUNCE_TIME}ms debounce): ${uniqueRenderCount}`); - console.debug('Render phases:', renderPhases); - console.debug(`Total duration: ${Date.now() - startTestTime}ms`); - console.debug('Render times (debounced):', Array.from(renderTimes)); - console.debug('Raw render times:', rawRenderTimes); - console.debug('=========================================='); - }; + let timeoutId: NodeJS.Timeout | null = null; E2ELogin().then((neededLogin) => { if (neededLogin) { @@ -56,38 +32,43 @@ const test = (config: NativeConfig) => { openReportPromise .then(() => { - logStats(); + console.debug(`Total renders: ${totalRenderCount}`); + E2EClient.submitTestResults({ + branch: Config.E2E_BRANCH, + name, + metric: totalRenderCount, + unit: 'renders', + }); + if (timeoutId) { + clearTimeout(timeoutId); + } console.debug('[E2E] Test completed'); E2EClient.submitTestDone(); }) .catch((err) => { + if (timeoutId) { + clearTimeout(timeoutId); + } console.debug('[E2E] Error:', err); console.error(err); }); - Performance.subscribeToMeasurements((entry) => { - if (entry.name === ' rendering') { - const currentTime = Date.now(); - const phase = entry.detail?.phase; - - // Track all renders - if (['mount', 'update', 'nested-update'].includes(phase)) { - totalRenderCount++; - rawRenderTimes.push(currentTime); - - // Increment phase counter - if (phase) { - renderPhases[phase]++; - } + const startTimer = () => { + // Clear existing timeout if any + if (timeoutId) { + clearTimeout(timeoutId); + } - // Check if this render is unique (debounced) - const isUniqueRender = !Array.from(renderTimes).some((time) => Math.abs(currentTime - time) < RENDER_DEBOUNCE_TIME); + // Set new timeout + timeoutId = setTimeout(() => { + console.debug('[E2E] No new renders for 5 seconds, completing test'); + openReportResolve(); + }, RERENDER_WAIT_TIME); + }; - if (isUniqueRender) { - renderTimes.add(currentTime); - uniqueRenderCount++; - } - } + Performance.subscribeToMeasurements((entry) => { + if (entry.name === ' rendering') { + totalRenderCount++; } if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { @@ -97,16 +78,8 @@ const test = (config: NativeConfig) => { } if (entry.name === CONST.TIMING.OPEN_REPORT) { - setTimeout(() => { - logStats(); - E2EClient.submitTestResults({ - branch: Config.E2E_BRANCH, - name, - metric: uniqueRenderCount, - unit: 'renders', - }); - openReportResolve(); - }, RERENDER_WAIT_TIME); + // Start initial timer + startTimer(); } }); }); From 9055c098cf2b6e6c6e46c96f2322646abc0221b4 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 27 Dec 2024 16:57:34 +0100 Subject: [PATCH 4/5] add const --- src/CONST.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 53197d41b85e..581c2eba90d9 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6448,6 +6448,12 @@ const CONST = { }, MIGRATED_USER_WELCOME_MODAL: 'migratedUserWelcomeModal', + + PERFORMANCE: { + SCREEN_KEYS: { + REPORT_SCREEN: ' rendering', + }, + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; From 2da71316fd798f957da950ecd64637e9339dac69 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 27 Dec 2024 16:57:48 +0100 Subject: [PATCH 5/5] use withRenderTrace --- src/pages/home/ReportScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index e7a697c8f601..7b86acc8a71a 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -887,5 +887,5 @@ ReportScreen.displayName = 'ReportScreen'; const MemoizedReportScreen = memo(ReportScreen, (prevProps, nextProps) => prevProps.currentReportID === nextProps.currentReportID && lodashIsEqual(prevProps.route, nextProps.route)); const WithCurrentReportID = withCurrentReportID(MemoizedReportScreen); -export default Performance.withRenderTrace({id: ' rendering'})(WithCurrentReportID); +export default Performance.withRenderTrace({id: CONST.PERFORMANCE.SCREEN_KEYS.REPORT_SCREEN})(WithCurrentReportID as React.ComponentType); export type {ReportScreenProps, ReportScreenNavigationProps};