run method
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();
}
}