Connects to a Flutter application.
Resumes the application if it is currently paused (e.g. at a breakpoint).
dartVmServiceUrl is the URL to Dart observatory (a.k.a. VM service). If
not specified, the URL specified by the VM_SERVICE_URL environment
variable is used, or 'http://localhost:8183'.
Source
static Future<FlutterDriver> connect({String dartVmServiceUrl}) async {
dartVmServiceUrl ??= Platform.environment['VM_SERVICE_URL'];
dartVmServiceUrl ??= 'http://localhost:8183';
// Connect to Dart VM servcies
_log.info('Connecting to Flutter application at $dartVmServiceUrl');
VMServiceClientConnection connection = await vmServiceConnectFunction(dartVmServiceUrl);
VMServiceClient client = connection.client;
VM vm = await client.getVM();
_log.trace('Looking for the isolate');
VMIsolate isolate = await vm.isolates.first.loadRunnable();
// TODO(yjbanov): vm_service_client does not support "None" pause event yet.
// It is currently reported as `null`, but we cannot rely on it because
// eventually the event will be reported as a non-`null` object. For now,
// list all the events we know about. Later we'll check for "None" event
// explicitly.
//
// See: https://github.com/dart-lang/vm_service_client/issues/4
if (isolate.pauseEvent is! VMPauseStartEvent &&
isolate.pauseEvent is! VMPauseExitEvent &&
isolate.pauseEvent is! VMPauseBreakpointEvent &&
isolate.pauseEvent is! VMPauseExceptionEvent &&
isolate.pauseEvent is! VMPauseInterruptedEvent &&
isolate.pauseEvent is! VMResumeEvent) {
await new Future<Null>.delayed(new Duration(milliseconds: 300));
isolate = await vm.isolates.first.loadRunnable();
}
FlutterDriver driver = new FlutterDriver.connectedTo(client, connection.peer, isolate);
// Attempts to resume the isolate, but does not crash if it fails because
// the isolate is already resumed. There could be a race with other tools,
// such as a debugger, any of which could have resumed the isolate.
Future<dynamic> resumeLeniently() {
_log.trace('Attempting to resume isolate');
return isolate.resume().catchError((dynamic e) {
const int vmMustBePausedCode = 101;
if (e is rpc.RpcException && e.code == vmMustBePausedCode) {
// No biggie; something else must have resumed the isolate
_log.warning(
'Attempted to resume an already resumed isolate. This may happen '
'when we lose a race with another tool (usually a debugger) that '
'is connected to the same isolate.'
);
} else {
// Failed to resume due to another reason. Fail hard.
throw e;
}
});
}
// Attempt to resume isolate if it was paused
if (isolate.pauseEvent is VMPauseStartEvent) {
_log.trace('Isolate is paused at start.');
// Waits for a signal from the VM service that the extension is registered
Future<String> waitForServiceExtension() {
return isolate.onExtensionAdded.firstWhere((String extension) {
return extension == _kFlutterExtensionMethod;
});
}
// If the isolate is paused at the start, e.g. via the --start-paused
// option, then the VM service extension is not registered yet. Wait for
// it to be registered.
Future<dynamic> whenResumed = resumeLeniently();
Future<dynamic> whenServiceExtensionReady = Future.any/*<dynamic>*/(<Future<dynamic>>[
waitForServiceExtension(),
// We will never receive the extension event if the user does not
// register it. If that happens time out.
new Future<String>.delayed(const Duration(seconds: 10), () => 'timeout')
]);
await whenResumed;
_log.trace('Waiting for service extension');
dynamic signal = await whenServiceExtensionReady;
if (signal == 'timeout') {
throw new DriverError(
'Timed out waiting for Flutter Driver extension to become available. '
'Ensure your test app (often: lib/main.dart) imports '
'"package:flutter_driver/driver_extension.dart" and '
'calls enableFlutterDriverExtension() as the first call in main().'
);
}
} else if (isolate.pauseEvent is VMPauseExitEvent ||
isolate.pauseEvent is VMPauseBreakpointEvent ||
isolate.pauseEvent is VMPauseExceptionEvent ||
isolate.pauseEvent is VMPauseInterruptedEvent) {
// If the isolate is paused for any other reason, assume the extension is
// already there.
_log.trace('Isolate is paused mid-flight.');
await resumeLeniently();
} else if (isolate.pauseEvent is VMResumeEvent) {
_log.trace('Isolate is not paused. Assuming application is ready.');
} else {
_log.warning(
'Unknown pause event type ${isolate.pauseEvent.runtimeType}. '
'Assuming application is ready.'
);
}
// At this point the service extension must be installed. Verify it.
Health health = await driver.checkHealth();
if (health.status != HealthStatus.ok) {
await client.close();
throw new DriverError('Flutter application health check failed.');
}
_log.info('Connected to Flutter application.');
return driver;
}