pumpForDuration function

Future<void> pumpForDuration(
  1. WidgetTester tester, {
  2. required Duration duration,
  3. Duration step = const Duration(milliseconds: 100),
})

Pumps for a total duration, advancing time in steps of step.

Works correctly in both binding types:

  • Automated binding (FakeAsync): advances the fake clock in step increments until duration is reached, firing timers and rendering frames along the way.

  • Live/Preview binding: calls tester.pump() (no duration) in a tight loop until duration wall-clock time has elapsed. Each pump() renders immediately and yields to the event loop, letting real async work (API responses, state updates) progress between frames.

    Why not pump(duration) or Future.delayed + pump()? PreviewTestBinding's pump(duration) creates a Timer but only sets _expectingFrame = true after the Timer fires. If a framework-scheduled frame (from setState/scheduleFrame) runs during the Timer wait, it sees _expectingFrame = false with fadePointers frame policy, skips super.handleBeginFrame(), and leaves _hasScheduledFrame stale. This prevents subsequent scheduleFrame() calls from requesting a new engine frame — deadlocking pump(). Calling pump() without duration avoids this by setting _expectingFrame = true synchronously before scheduleFrame(), so any engine frame callback sees it as expected.

Use this after a widget is visible to let animations/loading complete, or to drain pending timers at the end of a test.

Implementation

Future<void> pumpForDuration(
  WidgetTester tester, {
  required Duration duration,
  Duration step = const Duration(milliseconds: 100),
}) async {
  final stopwatch = Stopwatch()..start();
  if (isLiveBinding) {
    // In live/preview bindings: pump() without duration renders immediately
    // on the next vsync. Each await yields to the event loop, letting real
    // async work (API responses, Provider state updates, timers) progress.
    // The Stopwatch measures wall-clock time including rendering overhead,
    // so each iteration naturally takes some real time (~100-200ms with
    // gRPC frame capture), producing a reasonable number of frames.
    while (stopwatch.elapsed < duration) {
      await tester.pump();
    }
  } else {
    // In automated binding (FakeAsync): pump(step) advances the fake clock
    // synchronously, firing timers and rendering frames along the way.
    while (stopwatch.elapsed < duration) {
      final remaining = duration - stopwatch.elapsed;
      final pumpStep = remaining < step ? remaining : step;
      await tester.pump(pumpStep);
    }
  }
}