diff --git a/src/CONST.ts b/src/CONST.ts index 2d70c9355651..a9569315db9b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6494,6 +6494,12 @@ const CONST = { MIGRATED_USER_WELCOME_MODAL: 'migratedUserWelcomeModal', + PERFORMANCE: { + SCREEN_KEYS: { + REPORT_SCREEN: ' rendering', + }, + }, + PRODUCT_TRAINING_TOOLTIP_NAMES: { CONCEIRGE_LHN_GBR: 'conciergeLHNGBR', RENAME_SAVED_SEARCH: 'renameSavedSearch', 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/libs/E2E/tests/chatRerenderingTest.e2e.ts b/src/libs/E2E/tests/chatRerenderingTest.e2e.ts new file mode 100644 index 000000000000..89dc53ff6186 --- /dev/null +++ b/src/libs/E2E/tests/chatRerenderingTest.e2e.ts @@ -0,0 +1,88 @@ +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 = 7000; + +const test = (config: NativeConfig) => { + console.debug('=========================================='); + console.debug('[E2E] Starting rerender test'); + console.debug('=========================================='); + + const reportID = getConfigValueOrThrow('reportID', config); + const name = getConfigValueOrThrow('name', config); + + let totalRenderCount = 0; + let timeoutId: NodeJS.Timeout | null = null; + + E2ELogin().then((neededLogin) => { + if (neededLogin) { + return waitForAppLoaded().then(() => E2EClient.submitTestDone()); + } + + const [openReportPromise, openReportResolve] = getPromiseWithResolve(); + + openReportPromise + .then(() => { + 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); + }); + + const startTimer = () => { + // Clear existing timeout if any + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Set new timeout + timeoutId = setTimeout(() => { + console.debug('[E2E] No new renders for 5 seconds, completing test'); + openReportResolve(); + }, RERENDER_WAIT_TIME); + }; + + Performance.subscribeToMeasurements((entry) => { + if (entry.name === ' rendering') { + totalRenderCount++; + } + + 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) { + // Start initial timer + startTimer(); + } + }); + }); +}; + +export default test; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 73fed14af87c..c260b106a14b 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'; @@ -281,6 +282,11 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro const didSubscribeToReportLeavingEvents = useRef(false); const [showSoftInputOnFocus, setShowSoftInputOnFocus] = useState(false); + const renderCount = useRef(0); + useEffect(() => { + renderCount.current += 1; + console.log(`+++++++++++ Unique rerenders: SCREEN #${renderCount.current}`); + }); useEffect(() => { if (!report?.reportID || shouldHideReport) { wasReportAccessibleRef.current = false; @@ -870,4 +876,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: CONST.PERFORMANCE.SCREEN_KEYS.REPORT_SCREEN})(WithCurrentReportID as React.ComponentType); +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', + }, }, };