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