fix(test): override FlutterError.onError after app.main() to fix E2E hang
app.main() synchronously sets FlutterError.onError to its crash-screen handler, overwriting the filter the test had registered first. The test binding's _runTest finally-block checks FlutterError.onError != _recordError and fires assertion '_pendingExceptionDetails != null', which prevents the integration test framework from calling exit() — causing the process to hang for the full 360-second timeout. Fix: capture the binding's error recorder (bindingError) before app.main(), call app.main() first, then install the DEFUNCT/DISPOSED filter pointing at bindingError, and restore to bindingError in teardown. This keeps the crash handler from interfering with the test binding's error tracking. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
cc108b4788
commit
a4cbe35b0f
@@ -130,17 +130,12 @@ void main() {
|
|||||||
addTearDown(tester.view.resetPhysicalSize);
|
addTearDown(tester.view.resetPhysicalSize);
|
||||||
addTearDown(tester.view.resetDevicePixelRatio);
|
addTearDown(tester.view.resetDevicePixelRatio);
|
||||||
|
|
||||||
// On Android, the keyboard-dismiss / window-resize cycle can trigger
|
// Capture the test binding's error recorder BEFORE app.main() so we can
|
||||||
// one final layout pass on already-disposed render objects (DEFUNCT).
|
// restore it in teardown. app.main() sets its own FlutterError.onError
|
||||||
// These spurious overflow errors have no effect on real functionality;
|
// (crash-screen handler); we must override it AFTER the call so our
|
||||||
// filter them so they don't fail the test.
|
// filter takes precedence, yet teardown still restores to the binding's
|
||||||
final prevError = FlutterError.onError;
|
// recorder (not to the crash handler).
|
||||||
FlutterError.onError = (details) {
|
final bindingError = FlutterError.onError;
|
||||||
final msg = details.toString();
|
|
||||||
if (msg.contains('DEFUNCT') || msg.contains('DISPOSED')) return;
|
|
||||||
prevError?.call(details);
|
|
||||||
};
|
|
||||||
addTearDown(() => FlutterError.onError = prevError);
|
|
||||||
|
|
||||||
_log('app start');
|
_log('app start');
|
||||||
app.main(
|
app.main(
|
||||||
@@ -155,6 +150,18 @@ void main() {
|
|||||||
accountConnectionStatusProvider.overrideWith((ref, _) async {}),
|
accountConnectionStatusProvider.overrideWith((ref, _) async {}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Override the crash handler that app.main() just installed with a
|
||||||
|
// filter that forwards non-spurious errors to the binding's recorder.
|
||||||
|
// On Android/Linux, keyboard-dismiss or teardown can produce
|
||||||
|
// DEFUNCT/DISPOSED layout errors; discard those silently.
|
||||||
|
FlutterError.onError = (details) {
|
||||||
|
final msg = details.toString();
|
||||||
|
if (msg.contains('DEFUNCT') || msg.contains('DISPOSED')) return;
|
||||||
|
bindingError?.call(details);
|
||||||
|
};
|
||||||
|
addTearDown(() => FlutterError.onError = bindingError);
|
||||||
|
|
||||||
await pumpUntil(tester, find.text('Welcome to SharedInbox'));
|
await pumpUntil(tester, find.text('Welcome to SharedInbox'));
|
||||||
_log('app settled');
|
_log('app settled');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user