connect static method
- String? dartVmServiceUrl,
- bool printCommunication = false,
- bool logCommunicationToFile = true,
- int? isolateNumber,
- Pattern? fuchsiaModuleTarget,
- Map<
String, dynamic> ? headers,
override
Connects to a Flutter application.
See FlutterDriver.connect for more documentation.
Implementation
static Future<FlutterDriver> connect({
String? dartVmServiceUrl,
bool printCommunication = false,
bool logCommunicationToFile = true,
int? isolateNumber,
Pattern? fuchsiaModuleTarget,
Map<String, dynamic>? headers,
}) async {
// If running on a Fuchsia device, connect to the first isolate whose name
// matches FUCHSIA_MODULE_TARGET.
//
// If the user has already supplied an isolate number/URL to the Dart VM
// service, then this won't be run as it is unnecessary.
if (Platform.isFuchsia && isolateNumber == null) {
// TODO(awdavies): Use something other than print. On fuchsia
// `stderr`/`stdout` appear to have issues working correctly.
driverLog = (String source, String message) {
print('$source: $message'); // ignore: avoid_print
};
fuchsiaModuleTarget ??= Platform.environment['FUCHSIA_MODULE_TARGET'];
if (fuchsiaModuleTarget == null) {
throw DriverError(
'No Fuchsia module target has been specified.\n'
'Please make sure to specify the FUCHSIA_MODULE_TARGET '
'environment variable.'
);
}
final fuchsia.FuchsiaRemoteConnection fuchsiaConnection = await FuchsiaCompat.connect();
final List<fuchsia.IsolateRef> refs = await fuchsiaConnection.getMainIsolatesByPattern(fuchsiaModuleTarget);
if (refs.isEmpty) {
throw DriverError('Failed to get any isolate refs!');
}
final fuchsia.IsolateRef ref = refs.first;
isolateNumber = ref.number;
dartVmServiceUrl = ref.dartVm.uri.toString();
await fuchsiaConnection.stop();
FuchsiaCompat.cleanup();
}
dartVmServiceUrl ??= Platform.environment['VM_SERVICE_URL'];
if (dartVmServiceUrl == null) {
throw DriverError(
'Could not determine URL to connect to application.\n'
'Either the VM_SERVICE_URL environment variable should be set, or an explicit '
'URL should be provided to the FlutterDriver.connect() method.'
);
}
// Connect to Dart VM services
_log('Connecting to Flutter application at $dartVmServiceUrl');
final vms.VmService client = await vmServiceConnectFunction(dartVmServiceUrl, headers);
Future<vms.IsolateRef?> waitForRootIsolate() async {
bool checkIsolate(vms.IsolateRef ref) => ref.number == isolateNumber.toString();
while (true) {
final vms.VM vm = await client.getVM();
if (vm.isolates!.isEmpty || (isolateNumber != null && !vm.isolates!.any(checkIsolate))) {
await Future<void>.delayed(_kPauseBetweenReconnectAttempts);
continue;
}
return isolateNumber == null
? vm.isolates!.first
: vm.isolates!.firstWhere(checkIsolate);
}
}
// Refreshes the isolate state periodically until the isolate reports as
// being runnable.
Future<vms.Isolate> 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<void>.delayed(_kPauseBetweenIsolateRefresh);
} else {
return isolate;
}
}
}
final vms.IsolateRef isolateRef = (await _warnIfSlow<vms.IsolateRef?>(
future: waitForRootIsolate(),
timeout: kUnusuallyLongTimeout,
message: isolateNumber == null
? 'The root isolate is taking an unusually long time to start.'
: 'Isolate $isolateNumber is taking an unusually long time to start.',
))!;
_log('Isolate found with number: ${isolateRef.number}');
final vms.Isolate isolate = await _warnIfSlow<vms.Isolate>(
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,
isolate,
printCommunication: printCommunication,
logCommunicationToFile: logCommunicationToFile,
);
// 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<vms.Success> resumeLeniently() async {
_log('Attempting to resume isolate');
// Let subsequent isolates start automatically.
try {
final vms.Response result = await client.setFlag('pause_isolates_on_start', 'false');
if (result.type != 'Success') {
_log('setFlag failure: $result');
}
} catch (e) {
_log('Failed to set pause_isolates_on_start=false, proceeding. Error: $e');
}
return client.resume(isolate.id!).catchError((Object e) {
const int vmMustBePausedCode = 101;
if (e is vms.RPCError && e.code == vmMustBePausedCode) {
// No biggie; something else must have resumed the isolate
_log(
'Attempted to resume an already resumed isolate. This may happen '
'when another tool (usually a debugger) resumed the isolate '
'before the flutter_driver did.'
);
return vms.Success();
} else {
// Failed to resume due to another reason. Fail hard.
throw e; // ignore: only_throw_errors, proxying the error from upstream.
}
});
}
/// Waits for a signal from the VM service that the extension is registered.
///
/// Looks at the list of loaded extensions for the current [isolateRef], as
/// well as the stream of added extensions.
Future<void> waitForServiceExtension() async {
await client.streamListen(vms.EventStreams.kIsolate);
final Future<void> extensionAlreadyAdded = client
.getIsolate(isolateRef.id!)
.then((vms.Isolate isolate) async {
if (isolate.extensionRPCs!.contains(_flutterExtensionMethodName)) {
return;
}
// Never complete. Rely on the stream listener to find the service
// extension instead.
return Completer<void>().future;
});
final Completer<void> extensionAdded = Completer<void>();
late StreamSubscription<vms.Event> isolateAddedSubscription;
isolateAddedSubscription = client.onIsolateEvent.listen(
(vms.Event data) {
if (data.kind == vms.EventKind.kServiceExtensionAdded && data.extensionRPC == _flutterExtensionMethodName) {
extensionAdded.complete();
isolateAddedSubscription.cancel();
}
},
onError: extensionAdded.completeError,
cancelOnError: true,
);
await Future.any(<Future<void>>[
extensionAlreadyAdded,
extensionAdded.future,
]);
await isolateAddedSubscription.cancel();
await client.streamCancel(vms.EventStreams.kIsolate);
}
// Attempt to resume isolate if it was paused
if (isolate.pauseEvent!.kind == vms.EventKind.kPauseStart) {
_log('Isolate is paused at start.');
await resumeLeniently();
} 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.kPausePostRequest) {
// If the isolate is paused for any other reason, assume the extension is
// already there.
_log('Isolate is paused mid-flight.');
await resumeLeniently();
} else if (isolate.pauseEvent!.kind == vms.EventKind.kResume) {
_log('Isolate is not paused. Assuming application is ready.');
} else {
_log(
'Unknown pause event type ${isolate.pauseEvent.runtimeType}. '
'Assuming application is ready.'
);
}
// We will never receive the extension event if the user does not register
// it. If that happens, show a message but continue waiting.
await _warnIfSlow<void>(
future: waitForServiceExtension(),
timeout: kUnusuallyLongTimeout,
message: 'Flutter Driver extension is taking a long time 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().',
);
final Health health = await driver.checkHealth();
if (health.status != HealthStatus.ok) {
await client.dispose();
await client.onDone;
throw DriverError('Flutter application health check failed.');
}
_log('Connected to Flutter application.');
return driver;
}