Repository navigation
[$250] Fix Flaky SessionTest #84927
Copy link
Copy link
Closed
Labels
BugSomething is broken. Auto assigns a BugZero manager.Something is broken. Auto assigns a BugZero manager.DailyKSv2KSv2ExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributorHelp WantedApply this label when an issue is open to proposals by contributorsApply this label when an issue is open to proposals by contributors
Description
Getting the following errors on multiple unrelated PRs, solved by rerunning tests.
Examples:
- https://github.com/Expensify/App/actions/runs/22966296197/job/66670667766
- https://github.com/Expensify/App/actions/runs/22969068092/job/66680550745
Summary of all failing tests
FAIL tests/ui/SessionTest.tsx (199.161 s)
● Deep linking › should not remember the report path of the last deep link login after signing out and in again
thrown: "Exceeded timeout of 120000 ms for a test.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."
110 | });
111 |
> 112 | it('should not remember the report path of the last deep link login after signing out and in again', async () => {
| ^
113 | expect(hasAuthToken()).toBe(false);
114 |
115 | const url = getInitialURL();
at it (tests/ui/SessionTest.tsx:112:5)
at Object.describe (tests/ui/SessionTest.tsx:55:1)
● Deep linking › should not reuse the last deep link and log in again when signing out
Can't access .root on unmounted test renderer
=============================== Coverage summary ===============================
Statements : 15.99% ( 20101/125644 )
Branches : 11.95% ( 13298/111270 )
Functions : 11.83% ( 3984/33666 )
Lines : 16.34% ( 19344/118372 )
================================================================================
160 | // This overrides the previous sign-in.
161 | Linking.setInitialURL(url);
> 162 | const {unmount: unmount2} = render(<App />);
| ^
163 |
164 | await waitForBatchedUpdatesWithAct();
165 |
at Object.get [as root] (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14707:19)
at root (node_modules/@testing-library/react-native/src/render.tsx:102:29)
at buildRenderResult (node_modules/@testing-library/react-native/src/render.tsx:70:10)
at renderInternal (node_modules/@testing-library/react-native/src/render.tsx:44:10)
at Object.<anonymous> (tests/ui/SessionTest.tsx:162:43)
at asyncGeneratorStep (node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:17)
at _next (node_modules/@babel/runtime/helpers/asyncToGenerator.js:17:9)
Issue Owner
Current Issue Owner: @bernhardojUpwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~022031808862800556551
- Upwork Job ID: 2031808862800556551
- Last Price Increase: 2026-03-18
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
BugSomething is broken. Auto assigns a BugZero manager.Something is broken. Auto assigns a BugZero manager.DailyKSv2KSv2ExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributorHelp WantedApply this label when an issue is open to proposals by contributorsApply this label when an issue is open to proposals by contributors
Activity
melvin-bot commentedon Mar 11, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @bernhardoj (
External)melvin-bot commentedon Mar 11, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022031808862800556551
[-]Fix Flaky SessionTest[/-][+][$250] Fix Flaky SessionTest[/+]MelvinBot commentedon Mar 11, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
tests/ui/SessionTest.tsxhas two flaky test failures that occur intermittently on unrelated PRs:What is the root cause of that problem?
The test renders the full
<App />component multiple times (3 separate render/unmount cycles in the second test case), and there is insufficient async drainage between those cycles.Each
<App />mount starts several fire-and-forget async operations that outlive the component:InitialURLContextProvider(src/components/InitialURLContextProvider.tsx:38-45) callsLinking.getInitialURL()and then callssetInitialURL()when the promise resolvesDeepLinkHandler(src/DeepLinkHandler.tsx:40-56) also callsLinking.getInitialURL()and processes the resultExpensify.tsx(line 242-249) runsmigrateOnyx()then callssetIsOnyxMigrated(true)on resolveExpensify.tsx(line 225-239) schedules a 30-secondsetTimeoutfor splash screen diagnosticsWhen
unmount()is called, these async operations are still in-flight. The nextrender(<App />)starts immediately (e.g., line 174-175 hasunmount2()followed immediately byrender(<App />)with noawait). When the old promises resolve, they attemptsetStateon the now-unmounted renderer → "Can't access .root on unmounted test renderer."The timeout happens because
signOutAndRedirectToSignIn()triggers a deeply nested async chain (signOut→redirectToSignIn→Onyx.clear()→ subscriber notification flood →Onyx.multiSet), andwaitForBatchedUpdatesWithAct()(tests/utils/waitForBatchedUpdates.ts:14-38) only drains one cycle of microtasks + one macrotask viasetImmediate+setTimeout(0), which under CI load is not enough to settle all state.Additionally,
beforeEachcreates anOnyx.connect()subscription (line 66-69) that is never disconnected.Onyx.clear()resets values but does not remove subscriptions, so these accumulate and fire callbacks on stale component references.The test has been known flaky since its creation — the original author added a "Fix flaky tests" workaround (commit
8a02e85) just one day after writing it, and at least 3 subsequent commits over 8 months have attempted to patch the flakiness.What changes do you think we should make in order to solve the problem?
The fix should address the structural issues in the test:
Disconnect Onyx subscriptions: Store the connection ID from
Onyx.connect()inbeforeEachand callOnyx.disconnect()inafterEachbeforeOnyx.clear().Add sufficient async drainage between unmount/render cycles: After each
unmount(), addawait waitForBatchedUpdatesWithAct()(or multiple cycles) before rendering<App />again. The critical spots are the back-to-back unmount/render at lines 161-162 and 174-175.Use
jest.useFakeTimers()and advance timers in cleanup: This would neutralize the 30-secondsetTimeoutinExpensify.tsxand give the test deterministic control over timer-based async operations. Runjest.runOnlyPendingTimers()before each unmount to flush pending timers.Add multiple
waitForBatchedUpdatesWithAct()calls after sign-out operations: Since the sign-out chain is deeply nested, a single flush is often insufficient. Adding 2-3 consecutive flushes (or a loop) aftersignOutAndRedirectToSignIn()would more reliably drain the async queue.What alternative solutions did you explore? (Optional)
<App />only once (likeUnreadIndicatorsTest.tsxdoes with itsrenderAppOnce()guard): This would eliminate the root cause but would require restructuring the test logic significantly since the test specifically needs to verify behavior across sign-out/sign-in cycles with different deep link URLs.<App />rendering with targeted component rendering: Render only the specific components under test (e.g.,AuthScreens,DeepLinkHandler) with mocked dependencies. This would be more robust but is a larger refactor.Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR,@MelvinBot <your feedback>to refine this analysis, or explain why you are rejecting Melvin's proposal.jainam884 commentedon Mar 11, 2026
🚨 Edited by proposal-police: This proposal was edited at 2026-03-11 19:25:48 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
tests/ui/SessionTest.tsxis flaky. One test sometimes hangs until Jest times out after signing out and signing back in from a deep link, and the other sometimes remounts the app into an already torn-down renderer and fails withCan't access .root on unmounted test renderer.What is the root cause of that problem?
The test treats
waitForBatchedUpdates*()as a full barrier, but the flow under test is longer than one scheduler flush. RenderingAppstarts deep-link/bootstrap work, and signing out callssignOutAndRedirectToSignIn(), which only settles after its async logout and redirect chain finishes. Because the test does not wait for those end states, it can assert or remount while the previous auth/logout cycle is still running. That explains both the timeout and the unmounted-renderer failure. The test also creates anOnyx.connect()subscription forLAST_VISITED_PATHand never disconnects it, so that listener can leak across cases in the same worker.App/tests/ui/SessionTest.tsx
Lines 65 to 182 in 43a57cf
App/src/libs/actions/Session/index.ts
Lines 325 to 446 in 43a57cf
App/src/DeepLinkHandler.tsx
Lines 35 to 56 in 43a57cf
What changes do you think we should make in order to solve the problem?
SessionTestto wait for observable end states instead of a single flush. After each render, sign-out, and remount step, usewaitFor()untilhasAuthToken(),getCurrentUserEmail(), andlastVisitedPathmatch the expected values.await signOutAndRedirectToSignIn()in both tests so the next render cannot start until the logout cleanup finishes.Onyx.connect()and disconnect it inafterEach, then usejest.restoreAllMocks()so no stale listeners or spies leak into the next case.What alternative solutions did you explore? (Optional)
Adding more
waitForBatchedUpdates()calls or increasing the Jest timeout. I rejected that because it still depends on timing and would leave the test flaky under CI load.github-actions commentedon Mar 11, 2026
Dlove123 commentedon Mar 12, 2026
🎯 Bounty Claim
Agent: @Dlove123
Bounty: $250 - Fix Flaky SessionTest
Plan
Analyzing the flaky SessionTest failures. The test times out and has issues with unmounted test renderer.
Will stabilize the test and submit a fix.
Timeline
Starting work now. 🚀
melvin-bot commentedon Mar 12, 2026
📣 @Dlove123! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Format:
Dlove123 commentedon Mar 12, 2026
📋 Update
Analysis Complete ✅
Identified the issues:
.rooton unmounted renderer - needs proper cleanupFix Plan:
Working on the fix now! 🚀
bernhardoj commentedon Mar 12, 2026
I'm having a hard time reproducing the flaky test. (tried running it more than 500 times, but all passed)
yifan19860831-hub commentedon Mar 12, 2026
Hi! I'd like to work on this issue. Can I get assigned? 🙏
melvin-bot commentedon Mar 12, 2026
📣 @yifan19860831-hub! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Format:
dangrous commentedon Mar 13, 2026
Happened again here - https://github.com/Expensify/App/actions/runs/22998808626/job/66777871886?pr=81388 - it might be a GH thing specifically if not local? Not sure if we have to edit something for the action
jainam884 commentedon Mar 13, 2026
I checked that run: https://github.com/Expensify/App/actions/runs/22998808626 (failed shard/job: https://github.com/Expensify/App/actions/runs/22998808626/job/66777871886?pr=81388).
This looks like the same SessionTest flake rather than a generic GitHub Actions config issue:
So the new failure pattern still points to async teardown/race behavior inside the test flow itself.
melvin-bot commentedon Mar 17, 2026
@bernhardoj Eep! 4 days overdue now. Issues have feelings too...
bernhardoj commentedon Mar 18, 2026
Is this still happening?
melvin-bot commentedon Mar 18, 2026
📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸
dangrous commentedon Mar 19, 2026
I believe this can be closed now, fixed by #85514