connect method

Future<FlutterDriver> connect (
  1. {String dartVmServiceUrl,
  2. bool printCommunication: false,
  3. bool logCommunicationToFile: true,
  4. int isolateNumber,
  5. Pattern fuchsiaModuleTarget,
  6. 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');
    };
    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);
    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 VMServiceClientConnection connection =
  await vmServiceConnectFunction(dartVmServiceUrl, headers: headers);
  final VMServiceClient client = connection.client;
  final VM vm = await client.getVM();
  final VMIsolateRef isolateRef = isolateNumber ==
      null ? vm.isolates.first :
  vm.isolates.firstWhere(
          (VMIsolateRef isolate) => isolate.number == isolateNumber);
  _log('Isolate found with number: ${isolateRef.number}');

  VMIsolate isolate = await isolateRef.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) {
    isolate = await isolateRef.loadRunnable();
  }

  final VMServiceFlutterDriver driver = VMServiceFlutterDriver.connectedTo(
    client, connection.peer, isolate,
    printCommunication: printCommunication,
    logCommunicationToFile: logCommunicationToFile,
  );

  driver._dartVmReconnectUrl = dartVmServiceUrl;

  // 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('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(
            '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;
      }
    });
  }

  /// 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 {
    final Future<void> extensionAlreadyAdded = isolateRef
      .loadRunnable()
      .then((VMIsolate 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>();
    StreamSubscription<String> isolateAddedSubscription;
    isolateAddedSubscription = isolate.onExtensionAdded.listen(
      (String extensionName) {
        if (extensionName == _flutterExtensionMethodName) {
          extensionAdded.complete();
          isolateAddedSubscription.cancel();
        }
      },
      onError: extensionAdded.completeError,
      cancelOnError: true);

    await Future.any(<Future<void>>[
      extensionAlreadyAdded,
      extensionAdded.future,
    ]);
  }

  /// Tells the Dart VM Service to notify us about "Isolate" events.
  ///
  /// This is a workaround for an issue in package:vm_service_client, which
  /// subscribes to the "Isolate" stream lazily upon subscription, which
  /// results in lost events.
  ///
  /// Details: https://github.com/dart-lang/vm_service_client/issues/17
  Future<void> enableIsolateStreams() async {
    await connection.peer.sendRequest('streamListen', <String, String>{
      'streamId': 'Isolate',
    });
  }

  // Attempt to resume isolate if it was paused
  if (isolate.pauseEvent is VMPauseStartEvent) {
    _log('Isolate is paused at start.');

    await resumeLeniently();
  } 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('Isolate is paused mid-flight.');
    await resumeLeniently();
  } else if (isolate.pauseEvent is VMResumeEvent) {
    _log('Isolate is not paused. Assuming application is ready.');
  } else {
    _log(
        'Unknown pause event type ${isolate.pauseEvent.runtimeType}. '
            'Assuming application is ready.'
    );
  }

  await enableIsolateStreams();

  // 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.close();
    throw DriverError('Flutter application health check failed.');
  }

  _log('Connected to Flutter application.');
  return driver;
}