blob: d96ee4abb8d08f334ad094cfcbd738d4cfcb616d [file] [log] [blame]
Jonah Williams183fe752020-10-26 10:11:30 -07001// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Jonah Williamsa3863b62021-05-17 17:39:03 -07005import 'dart:async';
6
Jonah Williams183fe752020-10-26 10:11:30 -07007import 'package:dds/dds.dart' as dds;
8import 'package:file/file.dart';
9import 'package:meta/meta.dart';
Jonah Williamsed5d8712020-10-28 17:32:05 -070010import 'package:package_config/package_config_types.dart';
Jonah Williams183fe752020-10-26 10:11:30 -070011import 'package:vm_service/vm_service.dart' as vm_service;
12
13import '../application_package.dart';
14import '../base/common.dart';
15import '../base/logger.dart';
16import '../base/process.dart';
17import '../build_info.dart';
18import '../device.dart';
Jonah Williamsa3863b62021-05-17 17:39:03 -070019import '../resident_runner.dart';
Jonah Williams4ae68a32021-04-01 13:23:40 -070020import '../sksl_writer.dart';
Jonah Williams183fe752020-10-26 10:11:30 -070021import '../vmservice.dart';
22import 'web_driver_service.dart';
23
24class FlutterDriverFactory {
25 FlutterDriverFactory({
Jonah Williams92034482022-06-15 13:02:07 -070026 required ApplicationPackageFactory applicationPackageFactory,
27 required Logger logger,
28 required ProcessUtils processUtils,
29 required String dartSdkPath,
30 required DevtoolsLauncher devtoolsLauncher,
Jonah Williams183fe752020-10-26 10:11:30 -070031 }) : _applicationPackageFactory = applicationPackageFactory,
32 _logger = logger,
33 _processUtils = processUtils,
Jonah Williamsa3863b62021-05-17 17:39:03 -070034 _dartSdkPath = dartSdkPath,
35 _devtoolsLauncher = devtoolsLauncher;
Jonah Williams183fe752020-10-26 10:11:30 -070036
37 final ApplicationPackageFactory _applicationPackageFactory;
38 final Logger _logger;
39 final ProcessUtils _processUtils;
40 final String _dartSdkPath;
Jonah Williamsa3863b62021-05-17 17:39:03 -070041 final DevtoolsLauncher _devtoolsLauncher;
Jonah Williams183fe752020-10-26 10:11:30 -070042
43 /// Create a driver service for running `flutter drive`.
44 DriverService createDriverService(bool web) {
45 if (web) {
46 return WebDriverService(
Yegor81c91922021-07-14 08:46:38 -070047 logger: _logger,
Jonah Williams183fe752020-10-26 10:11:30 -070048 processUtils: _processUtils,
49 dartSdkPath: _dartSdkPath,
50 );
51 }
52 return FlutterDriverService(
53 logger: _logger,
54 processUtils: _processUtils,
55 dartSdkPath: _dartSdkPath,
56 applicationPackageFactory: _applicationPackageFactory,
Jonah Williamsa3863b62021-05-17 17:39:03 -070057 devtoolsLauncher: _devtoolsLauncher,
Jonah Williams183fe752020-10-26 10:11:30 -070058 );
59 }
60}
61
62/// An interface for the `flutter driver` integration test operations.
63abstract class DriverService {
64 /// Install and launch the application for the provided [device].
65 Future<void> start(
66 BuildInfo buildInfo,
67 Device device,
68 DebuggingOptions debuggingOptions,
69 bool ipv6, {
Jonah Williams92034482022-06-15 13:02:07 -070070 File? applicationBinary,
71 String? route,
72 String? userIdentifier,
73 String? mainPath,
Jonah Williams183fe752020-10-26 10:11:30 -070074 Map<String, Object> platformArgs = const <String, Object>{},
75 });
76
Jonah Williamsd306c372020-10-28 13:55:29 -070077 /// If --use-existing-app is provided, configured the correct VM Service URI.
78 Future<void> reuseApplication(
79 Uri vmServiceUri,
80 Device device,
81 DebuggingOptions debuggingOptions,
82 bool ipv6,
83 );
84
Jonah Williams183fe752020-10-26 10:11:30 -070085 /// Start the test file with the provided [arguments] and [environment], returning
86 /// the test process exit code.
Jonah Williamsa3863b62021-05-17 17:39:03 -070087 ///
88 /// if [profileMemory] is provided, it will be treated as a file path to write a
89 /// devtools memory profile.
Jonah Williams183fe752020-10-26 10:11:30 -070090 Future<int> startTest(
91 String testFile,
92 List<String> arguments,
Jonah Williamsed5d8712020-10-28 17:32:05 -070093 Map<String, String> environment,
94 PackageConfig packageConfig, {
Jonah Williams92034482022-06-15 13:02:07 -070095 bool? headless,
96 String? chromeBinary,
97 String? browserName,
98 bool? androidEmulator,
99 int? driverPort,
Gustl2247f54ac2022-06-24 19:14:08 +0200100 List<String> webBrowserFlags,
Jonah Williams92034482022-06-15 13:02:07 -0700101 List<String>? browserDimension,
102 String? profileMemory,
Jonah Williams183fe752020-10-26 10:11:30 -0700103 });
104
105 /// Stop the running application and uninstall it from the device.
106 ///
107 /// If [writeSkslOnExit] is non-null, will connect to the VM Service
108 /// and write SkSL to the file. This is only supported on mobile and
109 /// desktop devices.
110 Future<void> stop({
Jonah Williams92034482022-06-15 13:02:07 -0700111 File? writeSkslOnExit,
112 String? userIdentifier,
Jonah Williams183fe752020-10-26 10:11:30 -0700113 });
114}
115
116/// An implementation of the driver service that connects to mobile and desktop
117/// applications.
118class FlutterDriverService extends DriverService {
119 FlutterDriverService({
Jonah Williams92034482022-06-15 13:02:07 -0700120 required ApplicationPackageFactory applicationPackageFactory,
121 required Logger logger,
122 required ProcessUtils processUtils,
123 required String dartSdkPath,
124 required DevtoolsLauncher devtoolsLauncher,
Jonah Williams183fe752020-10-26 10:11:30 -0700125 @visibleForTesting VMServiceConnector vmServiceConnector = connectToVmService,
126 }) : _applicationPackageFactory = applicationPackageFactory,
127 _logger = logger,
128 _processUtils = processUtils,
129 _dartSdkPath = dartSdkPath,
Jonah Williamsa3863b62021-05-17 17:39:03 -0700130 _vmServiceConnector = vmServiceConnector,
131 _devtoolsLauncher = devtoolsLauncher;
Jonah Williams183fe752020-10-26 10:11:30 -0700132
133 static const int _kLaunchAttempts = 3;
134
135 final ApplicationPackageFactory _applicationPackageFactory;
136 final Logger _logger;
137 final ProcessUtils _processUtils;
138 final String _dartSdkPath;
139 final VMServiceConnector _vmServiceConnector;
Jonah Williamsa3863b62021-05-17 17:39:03 -0700140 final DevtoolsLauncher _devtoolsLauncher;
Jonah Williams183fe752020-10-26 10:11:30 -0700141
Jonah Williams92034482022-06-15 13:02:07 -0700142 Device? _device;
143 ApplicationPackage? _applicationPackage;
144 late String _vmServiceUri;
145 late FlutterVmService _vmService;
Jonah Williams183fe752020-10-26 10:11:30 -0700146
147 @override
148 Future<void> start(
149 BuildInfo buildInfo,
150 Device device,
151 DebuggingOptions debuggingOptions,
152 bool ipv6, {
Jonah Williams92034482022-06-15 13:02:07 -0700153 File? applicationBinary,
154 String? route,
155 String? userIdentifier,
Jonah Williams183fe752020-10-26 10:11:30 -0700156 Map<String, Object> platformArgs = const <String, Object>{},
Jonah Williams92034482022-06-15 13:02:07 -0700157 String? mainPath,
Jonah Williams183fe752020-10-26 10:11:30 -0700158 }) async {
159 if (buildInfo.isRelease) {
160 throwToolExit(
161 'Flutter Driver (non-web) does not support running in release mode.\n'
162 '\n'
163 'Use --profile mode for testing application performance.\n'
164 'Use --debug (default) mode for testing correctness (with assertions).'
165 );
166 }
167 _device = device;
168 final TargetPlatform targetPlatform = await device.targetPlatform;
169 _applicationPackage = await _applicationPackageFactory.getPackageForPlatform(
170 targetPlatform,
171 buildInfo: buildInfo,
172 applicationBinary: applicationBinary,
173 );
174 int attempt = 0;
Jonah Williams92034482022-06-15 13:02:07 -0700175 LaunchResult? result;
Jonah Williams183fe752020-10-26 10:11:30 -0700176 bool prebuiltApplication = applicationBinary != null;
177 while (attempt < _kLaunchAttempts) {
178 result = await device.startApp(
179 _applicationPackage,
180 mainPath: mainPath,
181 route: route,
182 debuggingOptions: debuggingOptions,
183 platformArgs: platformArgs,
184 userIdentifier: userIdentifier,
185 prebuiltApplication: prebuiltApplication,
186 );
187 if (result != null && result.started) {
188 break;
189 }
190 // On attempts past 1, assume the application is built correctly and re-use it.
191 attempt += 1;
192 prebuiltApplication = true;
193 _logger.printError('Application failed to start on attempt: $attempt');
194 }
195 if (result == null || !result.started) {
196 throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1);
197 }
Jonah Williamsd306c372020-10-28 13:55:29 -0700198 return reuseApplication(
Jonah Williams92034482022-06-15 13:02:07 -0700199 result.observatoryUri!,
Jonah Williamsd306c372020-10-28 13:55:29 -0700200 device,
201 debuggingOptions,
202 ipv6,
203 );
204 }
205
206 @override
207 Future<void> reuseApplication(
208 Uri vmServiceUri,
209 Device device,
210 DebuggingOptions debuggingOptions,
211 bool ipv6,
212 ) async {
Jonah Williams735f6ee2020-10-28 15:09:46 -0700213 Uri uri;
214 if (vmServiceUri.scheme == 'ws') {
Dan Field111c6ab2021-05-10 16:29:03 -0700215 final List<String> segments = vmServiceUri.pathSegments.toList();
216 segments.remove('ws');
217 uri = vmServiceUri.replace(scheme: 'http', path: segments.join('/'));
Jonah Williams735f6ee2020-10-28 15:09:46 -0700218 } else {
219 uri = vmServiceUri;
220 }
221 _vmServiceUri = uri.toString();
Jonah Williamsd306c372020-10-28 13:55:29 -0700222 _device = device;
Dan Field3ce6c1f2021-05-03 15:15:10 -0700223 if (debuggingOptions.enableDds) {
224 try {
225 await device.dds.startDartDevelopmentService(
226 uri,
Jenn Magder2d550322021-08-17 21:12:03 -0700227 hostPort: debuggingOptions.ddsPort,
228 ipv6: ipv6,
229 disableServiceAuthCodes: debuggingOptions.disableServiceAuthCodes,
Dan Field3ce6c1f2021-05-03 15:15:10 -0700230 logger: _logger,
231 );
232 _vmServiceUri = device.dds.uri.toString();
233 } on dds.DartDevelopmentServiceException {
234 // If there's another flutter_tools instance still connected to the target
235 // application, DDS will already be running remotely and this call will fail.
236 // This can be ignored to continue to use the existing remote DDS instance.
237 }
Jonah Williams183fe752020-10-26 10:11:30 -0700238 }
Dan Fieldf82794c2021-05-08 23:29:03 -0700239 _vmService = await _vmServiceConnector(uri, device: _device, logger: _logger);
Jonah Williams183fe752020-10-26 10:11:30 -0700240 final DeviceLogReader logReader = await device.getLogReader(app: _applicationPackage);
241 logReader.logLines.listen(_logger.printStatus);
242
Jonah Williamsa3b14c52021-02-24 15:40:33 -0800243 final vm_service.VM vm = await _vmService.service.getVM();
Jonah Williams183fe752020-10-26 10:11:30 -0700244 logReader.appPid = vm.pid;
245 }
246
247 @override
248 Future<int> startTest(
249 String testFile,
250 List<String> arguments,
Jonah Williamsed5d8712020-10-28 17:32:05 -0700251 Map<String, String> environment,
252 PackageConfig packageConfig, {
Jonah Williams92034482022-06-15 13:02:07 -0700253 bool? headless,
254 String? chromeBinary,
255 String? browserName,
256 bool? androidEmulator,
257 int? driverPort,
Gustl2247f54ac2022-06-24 19:14:08 +0200258 List<String> webBrowserFlags = const <String>[],
Jonah Williams92034482022-06-15 13:02:07 -0700259 List<String>? browserDimension,
260 String? profileMemory,
Jonah Williams183fe752020-10-26 10:11:30 -0700261 }) async {
Jonah Williamsa3863b62021-05-17 17:39:03 -0700262 if (profileMemory != null) {
263 unawaited(_devtoolsLauncher.launch(
264 Uri.parse(_vmServiceUri),
Jacob Richman1a0d8732021-07-08 14:29:41 -0700265 additionalArguments: <String>['--record-memory-profile=$profileMemory'],
Jonah Williamsa3863b62021-05-17 17:39:03 -0700266 ));
267 // When profiling memory the original launch future will never complete.
268 await _devtoolsLauncher.processStart;
269 }
270 try {
271 final int result = await _processUtils.stream(<String>[
272 _dartSdkPath,
273 ...<String>[...arguments, testFile, '-rexpanded'],
274 ], environment: <String, String>{
275 'VM_SERVICE_URL': _vmServiceUri,
276 ...environment,
277 });
278 return result;
279 } finally {
280 if (profileMemory != null) {
281 await _devtoolsLauncher.close();
282 }
283 }
Jonah Williams183fe752020-10-26 10:11:30 -0700284 }
285
286 @override
287 Future<void> stop({
Jonah Williams92034482022-06-15 13:02:07 -0700288 File? writeSkslOnExit,
289 String? userIdentifier,
Jonah Williams183fe752020-10-26 10:11:30 -0700290 }) async {
291 if (writeSkslOnExit != null) {
292 final FlutterView flutterView = (await _vmService.getFlutterViews()).first;
Jonah Williams428cafa2022-08-26 11:03:04 -0700293 final Map<String, Object?>? result = await _vmService.getSkSLs(
Jonah Williams183fe752020-10-26 10:11:30 -0700294 viewId: flutterView.id
Jonah Williams428cafa2022-08-26 11:03:04 -0700295 );
Jonah Williams92034482022-06-15 13:02:07 -0700296 await sharedSkSlWriter(_device!, result, outputFile: writeSkslOnExit, logger: _logger);
Jonah Williams183fe752020-10-26 10:11:30 -0700297 }
Jonah Williamsd306c372020-10-28 13:55:29 -0700298 // If the application package is available, stop and uninstall.
299 if (_applicationPackage != null) {
Jonah Williams92034482022-06-15 13:02:07 -0700300 if (!await _device!.stopApp(_applicationPackage, userIdentifier: userIdentifier)) {
Jonah Williams183fe752020-10-26 10:11:30 -0700301 _logger.printError('Failed to stop app');
302 }
Jonah Williams92034482022-06-15 13:02:07 -0700303 if (!await _device!.uninstallApp(_applicationPackage!, userIdentifier: userIdentifier)) {
Jonah Williamsd306c372020-10-28 13:55:29 -0700304 _logger.printError('Failed to uninstall app');
Jonah Williams183fe752020-10-26 10:11:30 -0700305 }
Jonah Williams92034482022-06-15 13:02:07 -0700306 } else if (_device!.supportsFlutterExit) {
Jonah Williamsd306c372020-10-28 13:55:29 -0700307 // Otherwise use the VM Service URI to stop the app as a best effort approach.
Jonah Williamsa3b14c52021-02-24 15:40:33 -0800308 final vm_service.VM vm = await _vmService.service.getVM();
Jonah Williams92034482022-06-15 13:02:07 -0700309 final vm_service.IsolateRef isolateRef = vm.isolates!
Jonah Williamsd306c372020-10-28 13:55:29 -0700310 .firstWhere((vm_service.IsolateRef element) {
Jonah Williams92034482022-06-15 13:02:07 -0700311 return !element.isSystemIsolate!;
312 });
313 unawaited(_vmService.flutterExit(isolateId: isolateRef.id!));
Jonah Williamsd306c372020-10-28 13:55:29 -0700314 } else {
315 _logger.printTrace('No application package for $_device, leaving app running');
Jonah Williams183fe752020-10-26 10:11:30 -0700316 }
Jonah Williams92034482022-06-15 13:02:07 -0700317 await _device!.dispose();
Jonah Williams183fe752020-10-26 10:11:30 -0700318 }
319}