run method

Future<void> run(
  1. DevelopOptions options
)

Runs the full develop flow: discover device, read config, bundle test, build, execute, and attach for hot restart.

Implementation

Future<void> run(DevelopOptions options) async {
  final config = _pubspecReader.read();
  final testDirectory = config.testDirectory;

  final testFinder = _testFinderFactory.create(testDirectory);
  final target = testFinder.findTest(options.target, config.testFileSuffix);
  _logger.detail('Received test target: $target');

  if (options.buildMode == BuildMode.release) {
    throwToolExit('Cannot use release build mode with develop');
  }

  if (options.generateBundle) {
    _testBundler.createDevelopTestBundle(testDirectory, target);
  }
  _testBundler.ensureEntrypoint(testDirectory);
  final entrypoint = _testBundler.getEntrypointFile(testDirectory);
  final signalSubscriptions = _registerProxyCleanupOnSignals(testDirectory);
  Future<void> cleanupProxy() async {
    _testBundler.deleteEntrypointProxy(testDirectory);
  }

  final androidFlavor = options.flavor ?? config.android.flavor;
  final iosFlavor = options.flavor ?? config.ios.flavor;
  if (androidFlavor != null) {
    _logger.detail('Received Android flavor: $androidFlavor');
  }
  if (iosFlavor != null) {
    _logger.detail('Received iOS flavor: $iosFlavor');
  }

  if (options.buildName != null) {
    _logger.detail('Received build name: ${options.buildName}');
  }
  if (options.buildNumber != null) {
    _logger.detail('Received build number: ${options.buildNumber}');
  }

  final devices = await _deviceFinder.find(
    options.devices,
    flutterCommand: options.flutterCommand,
  );
  final device = devices.single;
  _device = device;

  if (options.checkCompatibility) {
    await _compatibilityChecker.checkVersionsCompatibility(
      flutterCommand: options.flutterCommand,
      targetPlatform: device.targetPlatform,
    );
  }

  // `flutter logs` doesn't work on macOS, so we don't support it for now
  // https://github.com/leancodepl/patrol/issues/1974
  if (device.targetPlatform == TargetPlatform.macOS) {
    throwToolExit('macOS is not supported with develop');
  }

  if (device.targetPlatform == TargetPlatform.linux ||
      device.targetPlatform == TargetPlatform.windows) {
    throwToolExit('Desktop (Linux/Windows) is not supported with develop');
  }

  // Web develop works via flutter run -d chrome with CDP-based
  // screenshot/video. Hot restart of test code is limited (changes
  // outside /lib are not hot-restarted, see flutter#175318).

  _logger.detail('Received device: ${device.name} (${device.id})');

  final packageName = options.packageName ?? config.android.packageName;
  final bundleId = options.bundleId ?? config.ios.bundleId;
  final androidAppName = options.appName ?? config.android.appName;
  final iosAppName = options.appName ?? config.ios.appName;
  final macosAppName = options.appName ?? config.macos.appName;

  String? iOSInstalledAppsEnvVariable;
  if (device.targetPlatform == TargetPlatform.iOS) {
    iOSInstalledAppsEnvVariable = await _iosTestBackend
        .getInstalledAppsEnvVariable(device.id);
  }

  final customDartDefines = {
    ..._dartDefinesReader.fromFile(),
    ..._dartDefinesReader.fromCli(args: options.dartDefines),
  };
  final internalDartDefines = {
    'PATROL_APP_PACKAGE_NAME': packageName,
    'PATROL_APP_BUNDLE_ID': bundleId,
    'PATROL_MACOS_APP_BUNDLE_ID': config.macos.bundleId,
    'PATROL_ANDROID_APP_NAME': androidAppName,
    'PATROL_IOS_APP_NAME': iosAppName,
    'PATROL_MACOS_APP_NAME': macosAppName,
    'INTEGRATION_TEST_SHOULD_REPORT_RESULTS_TO_NATIVE': 'false',
    'PATROL_TEST_LABEL_ENABLED': options.displayLabel.toString(),
    'PATROL_TEST_DIRECTORY': config.testDirectory,
    // develop-specific
    ...{
      'PATROL_HOT_RESTART': 'true',
      'PATROL_IOS_INSTALLED_APPS': iOSInstalledAppsEnvVariable,
    },
    'PATROL_TEST_SERVER_PORT': options.testServerPort.toString(),
    'PATROL_APP_SERVER_PORT': options.appServerPort.toString(),
  }.withNullsRemoved();

  final dartDefines = {...customDartDefines, ...internalDartDefines};
  _logger.detail(
    'Received ${dartDefines.length} --dart-define(s) '
    '(${customDartDefines.length} custom, '
    '${internalDartDefines.length} internal)',
  );
  for (final dartDefine in customDartDefines.entries) {
    _logger.detail('Received custom --dart-define: ${dartDefine.key}');
  }
  for (final dartDefine in internalDartDefines.entries) {
    _logger.detail(
      'Received internal --dart-define: '
      '${dartDefine.key}=${dartDefine.value}',
    );
  }

  final mergedDartDefines = mergeDartDefines(
    options.dartDefineFromFilePaths,
    dartDefines,
    _dartDefinesReader,
  );

  final flavor = switch (device.targetPlatform) {
    TargetPlatform.android => androidFlavor,
    TargetPlatform.iOS => iosFlavor,
    TargetPlatform.macOS => iosFlavor,
    _ => null,
  };

  final flutterOpts = FlutterAppOptions(
    command: options.flutterCommand,
    target: entrypoint.path,
    flavor: flavor,
    buildMode: options.buildMode,
    dartDefines: mergedDartDefines,
    dartDefineFromFilePaths: options.dartDefineFromFilePaths,
    buildName: options.buildName,
    buildNumber: options.buildNumber,
  );

  final androidOpts = AndroidAppOptions(
    flutter: flutterOpts,
    packageName: packageName,
    appServerPort: options.appServerPort,
    testServerPort: options.testServerPort,
    uninstall: options.uninstall,
  );

  final iosOpts = IOSAppOptions(
    flutter: flutterOpts,
    bundleId: bundleId,
    scheme: options.buildMode.createScheme(iosFlavor),
    configuration: options.buildMode.createConfiguration(iosFlavor),
    simulator: !device.real,
    osVersion: options.iosVersion ?? 'latest',
    appServerPort: options.appServerPort,
    testServerPort: options.testServerPort,
  );

  final macosOpts = MacOSAppOptions(
    flutter: flutterOpts,
    scheme: options.buildMode.createScheme(iosFlavor),
    configuration: options.buildMode.createConfiguration(iosFlavor),
    appServerPort: options.appServerPort,
    testServerPort: options.testServerPort,
  );

  final webOpts = WebAppOptions(flutter: flutterOpts);

  try {
    await _build(androidOpts, iosOpts, macosOpts, webOpts, device);
    await _preExecute(androidOpts, iosOpts, device, options.uninstall);
    await _execute(
      flutterOpts,
      androidOpts,
      iosOpts,
      macosOpts,
      webOpts,
      uninstall: options.uninstall,
      device: device,
      openDevtools: options.openDevtools,
      onQuitCleanup: cleanupProxy,
      showFlutterLogs: false,
      hideTestSteps: options.hideTestSteps,
      clearTestSteps: options.clearTestSteps,
    );
  } finally {
    for (final sub in signalSubscriptions) {
      await sub.cancel();
    }
    await cleanupProxy();
  }
}