connect method

Future<FlutterDriver> connect ({String dartVmServiceUrl, bool printCommunication: false, bool logCommunicationToFile: true, double timeoutMultiplier: _kDefaultTimeoutMultiplier, int isolateNumber, Duration isolateReadyTimeout, Pattern fuchsiaModuleTarget })

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. One or the other must be specified.

printCommunication determines whether the command communication between the test and the app should be printed to stdout.

logCommunicationToFile determines whether the command communication between the test and the app should be logged to flutter_driver_commands.log.

FlutterDriver multiplies all command timeouts by timeoutMultiplier.

isolateNumber (optional) determines the specific isolate to connect to. If this is left as null, will connect to the first isolate found running on dartVmServiceUrl.

isolateReadyTimeout determines how long after we connect to the VM service we will wait for the first isolate to become runnable. Explicitly specified non-null values are not affected by timeoutMultiplier.

fuchsiaModuleTarget (optional) If running on a Fuchsia Device, either this or the environment variable FUCHSIA_MODULE_TARGET must be set. This field will be ignored if isolateNumber is set, as this is already enough information to connect to an Isolate.

Implementation

static Future<FlutterDriver> connect({
  String dartVmServiceUrl,
  bool printCommunication = false,
  bool logCommunicationToFile = true,
  double timeoutMultiplier = _kDefaultTimeoutMultiplier,
  int isolateNumber,
  Duration isolateReadyTimeout,
  Pattern fuchsiaModuleTarget,
}) async {
  isolateReadyTimeout ??= _isolateLoadRunnableTimeout(timeoutMultiplier);
  // 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) {
    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\n'
          'environment variable.');
    }
    final fuchsia.FuchsiaRemoteConnection fuchsiaConnection =
        await FuchsiaCompat.connect();
    final List<fuchsia.IsolateRef> refs =
        await fuchsiaConnection.getMainIsolatesByPattern(fuchsiaModuleTarget);
    final fuchsia.IsolateRef ref = refs.first;
    await Future<void>.delayed(_fuchsiaDriveDelay(timeoutMultiplier));
    isolateNumber = ref.number;
    dartVmServiceUrl = ref.dartVm.uri.toString();
    await fuchsiaConnection.stop();
    FuchsiaCompat.cleanup();

    // TODO(awdavies): Use something other than print. On fuchsia
    // `stderr`/`stdout` appear to have issues working correctly.
    flutterDriverLog.listen(print);
  }

  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\n'
        'URL should be provided to the FlutterDriver.connect() method.');
  }

  // Connect to Dart VM services
  _log.info('Connecting to Flutter application at $dartVmServiceUrl');
  connectionTimeoutMultiplier = timeoutMultiplier;
  final VMServiceClientConnection connection =
      await vmServiceConnectFunction(dartVmServiceUrl);
  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.trace('Isolate found with number: ${isolateRef.number}');

  VMIsolate isolate = await isolateRef
      .loadRunnable()
      .timeout(isolateReadyTimeout, onTimeout: () {
    throw TimeoutException(
        'Timeout while waiting for the isolate to become runnable');
  });

  // 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 Future<void>.delayed(_shortTimeout(timeoutMultiplier) ~/ 10);
    isolate = await isolateRef.loadRunnable();
  }

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

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

  /// Waits for a signal from the VM service that the extension is registered.
  /// Returns [_flutterExtensionMethodName]
  Future<String> waitForServiceExtension() {
    return isolate.onExtensionAdded.firstWhere((String extension) {
      return extension == _flutterExtensionMethodName;
    });
  }

  /// 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.trace('Isolate is paused at start.');

    // 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.
    await enableIsolateStreams();
    final Future<dynamic> whenServiceExtensionReady = waitForServiceExtension();
    final Future<dynamic> whenResumed = resumeLeniently();
    await whenResumed;

    try {
      _log.trace('Waiting for service extension');
      // We will never receive the extension event if the user does not
      // register it. If that happens time out.
      await whenServiceExtensionReady.timeout(_longTimeout(timeoutMultiplier) * 2);
    } on TimeoutException catch (_) {
      throw 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.'
    );
  }

  // Invoked checkHealth and try to fix delays in the registration of Service
  // extensions
  Future<Health> checkHealth() async {
    try {
      // At this point the service extension must be installed. Verify it.
      return await driver.checkHealth();
    } on rpc.RpcException catch (e) {
      if (e.code != error_code.METHOD_NOT_FOUND) {
        rethrow;
      }
      _log.trace(
        'Check Health failed, try to wait for the service extensions to be'
        'registered.'
      );
      await enableIsolateStreams();
      await waitForServiceExtension().timeout(_longTimeout(timeoutMultiplier) * 2);
      return driver.checkHealth();
    }
  }

  final Health health = await checkHealth();
  if (health.status != HealthStatus.ok) {
    await client.close();
    throw DriverError('Flutter application health check failed.');
  }

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