Skip to content

Commit e1bb56a

Browse files
committed
test(firestore): wait out the emulator's Listen-stream backoff
test/firestore.test.tsx flakes in CI at roughly 18% per run (11 of 60 iterations, 2026-08-11). The Firestore emulator intermittently corrupts a Listen frame, so grpc-js reads four body bytes as a length prefix and reports RESOURCE_EXHAUSTED with an absurd size. The SDK special-cases that code with backoff.resetToMax(), parking the stream on a 60 second maximum backoff, then self-heals. This is an unresolved upstream emulator bug, firebase/firebase-tools#8654. There is no fixed version to pin, so the goal is to survive it rather than prevent it. Only the tests that read a document which does not exist need a live server round trip, so they are the only ones that can die; everything else is served from the local cache. Those tests currently abandon the stream after one second, so they fail while the SDK is still recovering. Raising both ceilings lets them wait the backoff out. waitFor polls every 50ms, so a larger budget costs nothing when the stream is healthy: measured at 80-114ms per test with the change across five runs, against 79-105ms without it across three. The budget is 120s rather than 60s because a third party hitting the same upstream bug found a 60s ceiling insufficient at least once, and observed a test self-heal at ~35s under a 120s ceiling. Verified: a simulated 65 second stall survives the new budget while failing under the default, and both typechecks are clean. Not verified, and it cannot be locally: that this rescues a real occurrence. The desync has never reproduced on a dev machine, so the evidence is CI once this lands, where desyncs should keep appearing while the flake stops. Refs #776
1 parent bad1433 commit e1bb56a

1 file changed

Lines changed: 17 additions & 6 deletions

File tree

test/firestore.test.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ describe('Firestore', () => {
3030
</FirebaseAppProvider>
3131
);
3232

33+
// The Firestore emulator intermittently corrupts a Listen frame
34+
// (firebase/firebase-tools#8654, unresolved upstream). The SDK reads it as
35+
// RESOURCE_EXHAUSTED and parks the stream on a 60s maximum backoff, then
36+
// self-heals. Only the tests that read a document which does not exist need a
37+
// live server round trip, so they are the only ones that can die; everything
38+
// else is served from the local cache. Wait the backoff out instead of
39+
// failing. `waitFor` polls, so this costs nothing when the stream is healthy.
40+
// Remove when #8654 is fixed upstream. See #776.
41+
const WAIT_OUT_BACKOFF = 120_000;
42+
const BACKOFF_TEST_TIMEOUT = 150_000;
43+
3344
afterEach(async () => {
3445
cleanup();
3546

@@ -108,11 +119,11 @@ describe('Firestore', () => {
108119

109120
const { result } = renderHook(() => useFirestoreDocData<any>(ref, { idField: 'id' }), { wrapper: Provider });
110121

111-
await waitFor(() => expect(result.current.status).toEqual('success'));
122+
await waitFor(() => expect(result.current.status).toEqual('success'), { timeout: WAIT_OUT_BACKOFF });
112123

113124
expect(result.current.status).toEqual('success');
114125
expect(result.current.data).toBeUndefined();
115-
});
126+
}, BACKOFF_TEST_TIMEOUT);
116127

117128
it('goes back into a loading state if you swap the query', async () => {
118129
const mockData = { a: 'hello' };
@@ -177,17 +188,17 @@ describe('Firestore', () => {
177188
const { result: subscribeResult } = renderHook(() => useFirestoreDoc(ref), { wrapper: Provider });
178189
const { result: onceResult } = renderHook(() => useFirestoreDocOnce(ref), { wrapper: Provider });
179190

180-
await waitFor(() => expect(subscribeResult.current.status).toEqual('success'));
181-
await waitFor(() => expect(onceResult.current.status).toEqual('success'));
191+
await waitFor(() => expect(subscribeResult.current.status).toEqual('success'), { timeout: WAIT_OUT_BACKOFF });
192+
await waitFor(() => expect(onceResult.current.status).toEqual('success'), { timeout: WAIT_OUT_BACKOFF });
182193

183194
expect(onceResult.current.data.exists()).toEqual(false);
184195

185196
await act(() => setDoc(ref, { a: 'test' }));
186197

187-
await waitFor(() => expect(subscribeResult.current.data.exists()).toEqual(true));
198+
await waitFor(() => expect(subscribeResult.current.data.exists()).toEqual(true), { timeout: WAIT_OUT_BACKOFF });
188199

189200
expect(onceResult.current.data.exists()).toEqual(false);
190-
});
201+
}, BACKOFF_TEST_TIMEOUT);
191202
});
192203

193204
describe('useFirestoreDocDataOnce', () => {

0 commit comments

Comments
 (0)