diff --git a/packages/flutter_driver/lib/src/driver/vmservice_driver.dart b/packages/flutter_driver/lib/src/driver/vmservice_driver.dart index c275175bed..ec76c97de1 100644 --- a/packages/flutter_driver/lib/src/driver/vmservice_driver.dart +++ b/packages/flutter_driver/lib/src/driver/vmservice_driver.dart @@ -100,6 +100,19 @@ class VMServiceFlutterDriver extends FlutterDriver { } } + // Refreshes the isolate state periodically until the isolate reports as + // being runnable. + Future waitForIsolateToBeRunnable(vms.IsolateRef ref) async { + while (true) { + final vms.Isolate isolate = await client.getIsolate(ref.id!); + if (isolate.pauseEvent!.kind == vms.EventKind.kNone) { + await Future.delayed(_kPauseBetweenIsolateRefresh); + } else { + return isolate; + } + } + } + final vms.IsolateRef isolateRef = (await _warnIfSlow( future: waitForRootIsolate(), timeout: kUnusuallyLongTimeout, @@ -108,11 +121,13 @@ class VMServiceFlutterDriver extends FlutterDriver { : 'Isolate $isolateNumber is taking an unusually long time to start.', ))!; _log('Isolate found with number: ${isolateRef.number}'); - vms.Isolate isolate = await client.getIsolate(isolateRef.id!); - - if (isolate.pauseEvent!.kind == vms.EventKind.kNone) { - isolate = await client.getIsolate(isolateRef.id!); - } + final vms.Isolate isolate = await _warnIfSlow( + future: waitForIsolateToBeRunnable(isolateRef), + timeout: kUnusuallyLongTimeout, + message: 'The isolate ${isolateRef.number} is taking unusually long time ' + 'to initialize. It still reports ${vms.EventKind.kNone} as pause ' + 'event which is incorrect.', + ); final VMServiceFlutterDriver driver = VMServiceFlutterDriver.connectedTo( client, @@ -201,7 +216,8 @@ class VMServiceFlutterDriver extends FlutterDriver { } else if (isolate.pauseEvent!.kind == vms.EventKind.kPauseExit || isolate.pauseEvent!.kind == vms.EventKind.kPauseBreakpoint || isolate.pauseEvent!.kind == vms.EventKind.kPauseException || - isolate.pauseEvent!.kind == vms.EventKind.kPauseInterrupted) { + isolate.pauseEvent!.kind == vms.EventKind.kPauseInterrupted || + isolate.pauseEvent!.kind == vms.EventKind.kPausePostRequest) { // If the isolate is paused for any other reason, assume the extension is // already there. _log('Isolate is paused mid-flight.'); @@ -583,6 +599,9 @@ Future _waitAndConnect(String url, Map? headers) /// the VM service. const Duration _kPauseBetweenReconnectAttempts = Duration(seconds: 1); +/// The amount of time we wait prior to refreshing the isolate state. +const Duration _kPauseBetweenIsolateRefresh = Duration(milliseconds: 100); + // See `timeline_streams` in // https://github.com/dart-lang/sdk/blob/main/runtime/vm/timeline.cc List _timelineStreamsToString(List streams) { diff --git a/packages/flutter_driver/test/src/real_tests/flutter_driver_test.dart b/packages/flutter_driver/test/src/real_tests/flutter_driver_test.dart index c8076aebc4..894c280b71 100644 --- a/packages/flutter_driver/test/src/real_tests/flutter_driver_test.dart +++ b/packages/flutter_driver/test/src/real_tests/flutter_driver_test.dart @@ -189,6 +189,33 @@ void main() { ); }); + test('Refreshes isolate if it is not started for long time', () async { + fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kNone, timestamp: 0); + fakeClient.onGetIsolate = changeIsolateEventAfter( + 5, + vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 1), + ); + + final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: ''); + expect(driver, isNotNull); + expect( + fakeClient.connectionLog, + [ + 'getIsolate', + 'getIsolate', + 'getIsolate', + 'getIsolate', + 'getIsolate', + 'setFlag pause_isolates_on_start false', + 'resume', + 'streamListen Isolate', + 'getIsolate', + 'onIsolateEvent', + 'streamCancel Isolate', + ], + ); + }); + test('Connects to isolate number', () async { fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0); final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '', isolateNumber: int.parse(fakeIsolate.number!)); @@ -246,6 +273,14 @@ void main() { expectLogContains('Isolate is paused mid-flight'); }); + test('connects to isolate paused mid-flight after request', () async { + fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPausePostRequest, timestamp: 0); + + final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: ''); + expect(driver, isNotNull); + expectLogContains('Isolate is paused mid-flight'); + }); + // This test simulates a situation when we believe that the isolate is // currently paused, but something else (e.g. a debugger) resumes it before // we do. There's no need to fail as we should be able to drive the app @@ -1055,6 +1090,15 @@ vms.Response? makeFakeResponse( }); } +void Function(vms.Isolate) changeIsolateEventAfter(int gets, vms.Event nextEvent) { + return (vms.Isolate i) { + gets -= 1; + if (gets == 0) { + i.pauseEvent = nextEvent; + } + }; +} + class FakeFlutterWebConnection extends Fake implements FlutterWebConnection { @override bool supportsTimelineAction = false; @@ -1082,6 +1126,7 @@ class FakeVmService extends Fake implements vms.VmService { FakeVM? vm; bool failOnSetFlag = false; bool failOnResumeWith101 = false; + void Function(vms.Isolate)? onGetIsolate; final List connectionLog = []; @@ -1092,6 +1137,7 @@ class FakeVmService extends Fake implements vms.VmService { Future getIsolate(String isolateId) async { connectionLog.add('getIsolate'); if (isolateId == vm!.isolate!.id) { + onGetIsolate?.call(vm!.isolate!); return vm!.isolate!; } throw UnimplementedError('getIsolate called with unrecognized $isolateId');