Unverified Commit 1edec6fc authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Revert "[flutter_tools] refactor drive launch into separate service, split by...

Revert "[flutter_tools] refactor drive launch into separate service, split by mobile+desktop and web (#68451)" (#68845)

This reverts commit 2e75f52a.
parent b25ce5b1
...@@ -95,10 +95,7 @@ Future<void> main(List<String> args) async { ...@@ -95,10 +95,7 @@ Future<void> main(List<String> args) async {
DevicesCommand(), DevicesCommand(),
DoctorCommand(verbose: verbose), DoctorCommand(verbose: verbose),
DowngradeCommand(), DowngradeCommand(),
DriveCommand(verboseHelp: verboseHelp, DriveCommand(verboseHelp: verboseHelp),
fileSystem: globals.fs,
logger: globals.logger,
),
EmulatorsCommand(), EmulatorsCommand(),
FormatCommand(), FormatCommand(),
GenerateCommand(), GenerateCommand(),
......
...@@ -590,17 +590,13 @@ class AndroidDevice extends Device { ...@@ -590,17 +590,13 @@ class AndroidDevice extends Device {
} }
final bool traceStartup = platformArgs['trace-startup'] as bool ?? false; final bool traceStartup = platformArgs['trace-startup'] as bool ?? false;
_logger.printTrace('$this startApp');
ProtocolDiscovery observatoryDiscovery; ProtocolDiscovery observatoryDiscovery;
if (debuggingOptions.debuggingEnabled) { if (debuggingOptions.debuggingEnabled) {
observatoryDiscovery = ProtocolDiscovery.observatory( observatoryDiscovery = ProtocolDiscovery.observatory(
// Avoid using getLogReader, which returns a singleton instance, because the await getLogReader(),
// observatory discovery will dipose at the end. creating a new logger here allows
// logs to be surfaced normally during `flutter drive`.
await AdbLogReader.createLogReader(
this,
_processManager,
),
portForwarder: portForwarder, portForwarder: portForwarder,
hostPort: debuggingOptions.hostVmServicePort, hostPort: debuggingOptions.hostVmServicePort,
devicePort: debuggingOptions.deviceVmServicePort, devicePort: debuggingOptions.deviceVmServicePort,
...@@ -673,6 +669,8 @@ class AndroidDevice extends Device { ...@@ -673,6 +669,8 @@ class AndroidDevice extends Device {
// Wait for the service protocol port here. This will complete once the // Wait for the service protocol port here. This will complete once the
// device has printed "Observatory is listening on...". // device has printed "Observatory is listening on...".
_logger.printTrace('Waiting for observatory port to be available...'); _logger.printTrace('Waiting for observatory port to be available...');
// TODO(danrubel): Waiting for observatory services can be made common across all devices.
try { try {
Uri observatoryUri; Uri observatoryUri;
if (debuggingOptions.buildInfo.isDebug || debuggingOptions.buildInfo.isProfile) { if (debuggingOptions.buildInfo.isDebug || debuggingOptions.buildInfo.isProfile) {
......
...@@ -22,6 +22,9 @@ import 'assets.dart'; ...@@ -22,6 +22,9 @@ import 'assets.dart';
import 'common.dart'; import 'common.dart';
import 'localizations.dart'; import 'localizations.dart';
/// Whether web builds should call the platform initialization logic.
const String kInitializePlatform = 'InitializePlatform';
/// Whether the application has web plugins. /// Whether the application has web plugins.
const String kHasWebPlugins = 'HasWebPlugins'; const String kHasWebPlugins = 'HasWebPlugins';
...@@ -86,6 +89,7 @@ class WebEntrypointTarget extends Target { ...@@ -86,6 +89,7 @@ class WebEntrypointTarget extends Target {
@override @override
Future<void> build(Environment environment) async { Future<void> build(Environment environment) async {
final String targetFile = environment.defines[kTargetFile]; final String targetFile = environment.defines[kTargetFile];
final bool shouldInitializePlatform = environment.defines[kInitializePlatform] == 'true';
final bool hasPlugins = environment.defines[kHasWebPlugins] == 'true'; final bool hasPlugins = environment.defines[kHasWebPlugins] == 'true';
final Uri importUri = environment.fileSystem.file(targetFile).absolute.uri; final Uri importUri = environment.fileSystem.file(targetFile).absolute.uri;
// TODO(jonahwilliams): support configuration of this file. // TODO(jonahwilliams): support configuration of this file.
...@@ -133,7 +137,9 @@ import '$mainImport' as entrypoint; ...@@ -133,7 +137,9 @@ import '$mainImport' as entrypoint;
Future<void> main() async { Future<void> main() async {
registerPlugins(webPluginRegistry); registerPlugins(webPluginRegistry);
await ui.webOnlyInitializePlatform(); if ($shouldInitializePlatform) {
await ui.webOnlyInitializePlatform();
}
entrypoint.main(); entrypoint.main();
} }
'''; ''';
...@@ -146,7 +152,9 @@ import 'dart:ui' as ui; ...@@ -146,7 +152,9 @@ import 'dart:ui' as ui;
import '$mainImport' as entrypoint; import '$mainImport' as entrypoint;
Future<void> main() async { Future<void> main() async {
await ui.webOnlyInitializePlatform(); if ($shouldInitializePlatform) {
await ui.webOnlyInitializePlatform();
}
entrypoint.main(); entrypoint.main();
} }
'''; ''';
......
...@@ -384,6 +384,7 @@ known, it can be explicitly provided to attach via the command-line, e.g. ...@@ -384,6 +384,7 @@ known, it can be explicitly provided to attach via the command-line, e.g.
final FlutterDevice flutterDevice = await FlutterDevice.create( final FlutterDevice flutterDevice = await FlutterDevice.create(
device, device,
flutterProject: flutterProject,
fileSystemRoots: stringsArg('filesystem-root'), fileSystemRoots: stringsArg('filesystem-root'),
fileSystemScheme: stringArg('filesystem-scheme'), fileSystemScheme: stringArg('filesystem-scheme'),
target: stringArg('target'), target: stringArg('target'),
......
...@@ -25,6 +25,12 @@ class BuildWebCommand extends BuildSubCommand { ...@@ -25,6 +25,12 @@ class BuildWebCommand extends BuildSubCommand {
usesDartDefineOption(); usesDartDefineOption();
addEnableExperimentation(hide: !verboseHelp); addEnableExperimentation(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp);
argParser.addFlag('web-initialize-platform',
defaultsTo: true,
negatable: true,
hide: true,
help: 'Whether to automatically invoke webOnlyInitializePlatform.',
);
argParser.addFlag('csp', argParser.addFlag('csp',
defaultsTo: false, defaultsTo: false,
negatable: false, negatable: false,
...@@ -86,6 +92,7 @@ class BuildWebCommand extends BuildSubCommand { ...@@ -86,6 +92,7 @@ class BuildWebCommand extends BuildSubCommand {
flutterProject, flutterProject,
target, target,
buildInfo, buildInfo,
boolArg('web-initialize-platform'),
boolArg('csp'), boolArg('csp'),
stringArg('pwa-strategy'), stringArg('pwa-strategy'),
boolArg('source-maps') boolArg('source-maps')
......
...@@ -466,6 +466,7 @@ class AppDomain extends Domain { ...@@ -466,6 +466,7 @@ class AppDomain extends Domain {
final FlutterDevice flutterDevice = await FlutterDevice.create( final FlutterDevice flutterDevice = await FlutterDevice.create(
device, device,
flutterProject: flutterProject,
target: target, target: target,
buildInfo: options.buildInfo, buildInfo: options.buildInfo,
platform: globals.platform, platform: globals.platform,
......
...@@ -3,21 +3,29 @@ ...@@ -3,21 +3,29 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'package:dds/dds.dart' as dds;
import 'package:vm_service/vm_service_io.dart' as vm_service;
import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:webdriver/async_io.dart' as async_io;
import '../android/android_device.dart'; import '../android/android_device.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../convert.dart';
import '../dart/package_map.dart';
import '../device.dart'; import '../device.dart';
import '../drive/drive_service.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../project.dart';
import '../resident_runner.dart';
import '../runner/flutter_command.dart' show FlutterCommandResult, FlutterOptions; import '../runner/flutter_command.dart' show FlutterCommandResult, FlutterOptions;
import '../web/web_device.dart'; import '../vmservice.dart';
import '../web/web_runner.dart';
import 'run.dart'; import 'run.dart';
/// Runs integration (a.k.a. end-to-end) tests. /// Runs integration (a.k.a. end-to-end) tests.
...@@ -43,12 +51,7 @@ import 'run.dart'; ...@@ -43,12 +51,7 @@ import 'run.dart';
class DriveCommand extends RunCommandBase { class DriveCommand extends RunCommandBase {
DriveCommand({ DriveCommand({
bool verboseHelp = false, bool verboseHelp = false,
@visibleForTesting FlutterDriverFactory flutterDriverFactory, }) {
@required FileSystem fileSystem,
@required Logger logger,
}) : _flutterDriverFactory = flutterDriverFactory,
_fileSystem = fileSystem,
_logger = logger {
requiresPubspecYaml(); requiresPubspecYaml();
addEnableExperimentation(hide: !verboseHelp); addEnableExperimentation(hide: !verboseHelp);
...@@ -106,7 +109,7 @@ class DriveCommand extends RunCommandBase { ...@@ -106,7 +109,7 @@ class DriveCommand extends RunCommandBase {
'firefox', 'firefox',
'ios-safari', 'ios-safari',
'safari', 'safari',
], ]
) )
..addOption('browser-dimension', ..addOption('browser-dimension',
defaultsTo: '1600,1024', defaultsTo: '1600,1024',
...@@ -124,15 +127,10 @@ class DriveCommand extends RunCommandBase { ...@@ -124,15 +127,10 @@ class DriveCommand extends RunCommandBase {
..addOption('write-sksl-on-exit', ..addOption('write-sksl-on-exit',
help: help:
'Attempts to write an SkSL file when the drive process is finished ' 'Attempts to write an SkSL file when the drive process is finished '
'to the provided file, overwriting it if necessary.') 'to the provided file, overwriting it if necessary.',
..addMultiOption('test-arguments', help: 'Additional arguments to pass to the ' );
'Dart VM running The test script.');
} }
FlutterDriverFactory _flutterDriverFactory;
final FileSystem _fileSystem;
final Logger _logger;
@override @override
final String name = 'drive'; final String name = 'drive';
...@@ -142,15 +140,20 @@ class DriveCommand extends RunCommandBase { ...@@ -142,15 +140,20 @@ class DriveCommand extends RunCommandBase {
@override @override
final List<String> aliases = <String>['driver']; final List<String> aliases = <String>['driver'];
Device _device;
Device get device => _device;
bool get verboseSystemLogs => boolArg('verbose-system-logs');
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser); String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
@override /// Subscription to log messages printed on the device or simulator.
bool get startPausedDefault => true; // ignore: cancel_subscriptions
StreamSubscription<String> _deviceLogSubscription;
@override @override
Future<void> validateCommand() async { Future<void> validateCommand() async {
if (userIdentifier != null) { if (userIdentifier != null) {
final Device device = await findTargetDevice(); final Device device = await findTargetDevice(timeout: deviceDiscoveryTimeout);
if (device is! AndroidDevice) { if (device is! AndroidDevice) {
throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android'); throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
} }
...@@ -164,70 +167,229 @@ class DriveCommand extends RunCommandBase { ...@@ -164,70 +167,229 @@ class DriveCommand extends RunCommandBase {
if (testFile == null) { if (testFile == null) {
throwToolExit(null); throwToolExit(null);
} }
if (await _fileSystem.type(testFile) != FileSystemEntityType.file) {
throwToolExit('Test file not found: $testFile'); _device = await findTargetDevice(timeout: deviceDiscoveryTimeout);
}
final Device device = await findTargetDevice(includeUnsupportedDevices: stringArg('use-application-binary') == null);
if (device == null) { if (device == null) {
throwToolExit(null); throwToolExit(null);
} }
final bool web = device is WebServerDevice || device is ChromiumDevice; if (await globals.fs.type(testFile) != FileSystemEntityType.file) {
_flutterDriverFactory ??= FlutterDriverFactory( throwToolExit('Test file not found: $testFile');
applicationPackageFactory: ApplicationPackageFactory.instance, }
logger: _logger,
processUtils: globals.processUtils, String observatoryUri;
dartSdkPath: globals.artifacts.getArtifactPath(Artifact.engineDartBinary), ResidentRunner residentRunner;
);
final DriverService driverService = _flutterDriverFactory.createDriverService(web);
final BuildInfo buildInfo = getBuildInfo(); final BuildInfo buildInfo = getBuildInfo();
final DebuggingOptions debuggingOptions = createDebuggingOptions(); final bool isWebPlatform = await device.targetPlatform == TargetPlatform.web_javascript;
final File applicationBinary = stringArg('use-application-binary') == null final File applicationBinary = stringArg('use-application-binary') == null
? null ? null
: _fileSystem.file(stringArg('use-application-binary')); : globals.fs.file(stringArg('use-application-binary'));
final ApplicationPackage package = await applicationPackages.getPackageForPlatform(
await driverService.start( await device.targetPlatform,
buildInfo, buildInfo: buildInfo,
device,
debuggingOptions,
ipv6,
applicationBinary: applicationBinary, applicationBinary: applicationBinary,
route: route,
userIdentifier: userIdentifier,
mainPath: targetFile,
platformArgs: <String, Object>{
if (traceStartup)
'trace-startup': traceStartup,
if (web)
'--no-launch-chrome': true,
}
); );
if (argResults['use-existing-app'] == null) {
globals.printStatus('Starting application: $targetFile');
final int testResult = await driverService.startTest( if (buildInfo.isRelease && !isWebPlatform) {
testFile, // This is because we need VM service to be able to drive the app.
stringsArg('test-arguments'), // For Flutter Web, testing in release mode is allowed.
<String, String>{}, throwToolExit(
chromeBinary: stringArg('chrome-binary'), 'Flutter Driver (non-web) does not support running in release mode.\n'
headless: boolArg('headless'), '\n'
browserDimension: stringArg('browser-dimension').split(','), 'Use --profile mode for testing application performance.\n'
browserName: stringArg('browser-name'), 'Use --debug (default) mode for testing correctness (with assertions).'
driverPort: stringArg('driver-port') != null );
? int.tryParse(stringArg('driver-port')) }
: null,
androidEmulator: boolArg('android-emulator'), Uri webUri;
);
if (isWebPlatform) {
// Start Flutter web application for current test
final FlutterProject flutterProject = FlutterProject.current();
final FlutterDevice flutterDevice = await FlutterDevice.create(
device,
flutterProject: flutterProject,
target: targetFile,
buildInfo: buildInfo,
platform: globals.platform,
);
residentRunner = webRunnerFactory.createWebRunner(
flutterDevice,
target: targetFile,
flutterProject: flutterProject,
ipv6: ipv6,
debuggingOptions: getBuildInfo().isRelease ?
DebuggingOptions.disabled(
getBuildInfo(),
port: stringArg('web-port')
)
: DebuggingOptions.enabled(
getBuildInfo(),
port: stringArg('web-port'),
disablePortPublication: disablePortPublication,
),
stayResident: false,
urlTunneller: null,
);
final Completer<void> appStartedCompleter = Completer<void>.sync();
final int result = await residentRunner.run(
appStartedCompleter: appStartedCompleter,
route: route,
);
if (result != 0) {
throwToolExit(null, exitCode: result);
}
// Wait until the app is started.
await appStartedCompleter.future;
webUri = residentRunner.uri;
}
if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) { // Attempt to launch the application up to 3 times, to validate whether it
_logger.printStatus('Leaving the application running.'); // is possible to reduce flakiness by hardnening the launch code.
int attempt = 0;
LaunchResult result;
while (attempt < 3) {
// On attempts past 1, assume the application is built correctly and re-use it.
result = await appStarter(this, webUri, package, applicationBinary != null || attempt > 0);
if (result != null) {
break;
}
attempt += 1;
globals.printError('Application failed to start on attempt: $attempt');
}
if (result == null) {
throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1);
}
observatoryUri = result.observatoryUri.toString();
// TODO(bkonyi): add web support (https://github.com/flutter/flutter/issues/61259)
if (!isWebPlatform && !disableDds) {
try {
// If there's another flutter_tools instance still connected to the target
// application, DDS will already be running remotely and this call will fail.
// We can ignore this and continue to use the remote DDS instance.
await device.dds.startDartDevelopmentService(
Uri.parse(observatoryUri),
ddsPort,
ipv6,
disableServiceAuthCodes,
);
observatoryUri = device.dds.uri.toString();
} on dds.DartDevelopmentServiceException catch(_) {
globals.printTrace('Note: DDS is already connected to $observatoryUri.');
}
}
} else { } else {
final File skslFile = stringArg('write-sksl-on-exit') != null globals.printStatus('Will connect to already running application instance.');
? _fileSystem.file(stringArg('write-sksl-on-exit')) observatoryUri = stringArg('use-existing-app');
: null;
await driverService.stop(userIdentifier: userIdentifier, writeSkslOnExit: skslFile);
} }
if (testResult != 0) {
return FlutterCommandResult.fail(); final Map<String, String> environment = <String, String>{
'VM_SERVICE_URL': observatoryUri,
};
async_io.WebDriver driver;
// For web device, WebDriver session will be launched beforehand
// so that FlutterDriver can reuse it.
if (isWebPlatform) {
final Browser browser = _browserNameToEnum(
argResults['browser-name'].toString());
final String driverPort = argResults['driver-port'].toString();
// start WebDriver
try {
driver = await _createDriver(
driverPort,
browser,
argResults['headless'].toString() == 'true',
stringArg('chrome-binary'),
);
} on Exception catch (ex) {
throwToolExit(
'Unable to start WebDriver Session for Flutter for Web testing. \n'
'Make sure you have the correct WebDriver Server running at $driverPort. \n'
'Make sure the WebDriver Server matches option --browser-name. \n'
'$ex'
);
}
final bool isAndroidChrome = browser == Browser.androidChrome;
final bool useEmulator = argResults['android-emulator'] as bool;
// set window size
// for android chrome, skip such action
if (!isAndroidChrome) {
final List<String> dimensions = argResults['browser-dimension'].split(
',') as List<String>;
assert(dimensions.length == 2);
int x, y;
try {
x = int.parse(dimensions[0]);
y = int.parse(dimensions[1]);
} on FormatException catch (ex) {
throwToolExit('''
Dimension provided to --browser-dimension is invalid:
$ex
''');
}
final async_io.Window window = await driver.window;
await window.setLocation(const math.Point<int>(0, 0));
await window.setSize(math.Rectangle<int>(0, 0, x, y));
}
// add driver info to environment variables
environment.addAll(<String, String> {
'DRIVER_SESSION_ID': driver.id,
'DRIVER_SESSION_URI': driver.uri.toString(),
'DRIVER_SESSION_SPEC': driver.spec.toString(),
'DRIVER_SESSION_CAPABILITIES': json.encode(driver.capabilities),
'SUPPORT_TIMELINE_ACTION': (browser == Browser.chrome).toString(),
'FLUTTER_WEB_TEST': 'true',
'ANDROID_CHROME_ON_EMULATOR': (isAndroidChrome && useEmulator).toString(),
});
} }
try {
await testRunner(
<String>[
if (buildInfo.dartExperiments.isNotEmpty)
'--enable-experiment=${buildInfo.dartExperiments.join(',')}',
if (buildInfo.nullSafetyMode == NullSafetyMode.sound)
'--sound-null-safety',
if (buildInfo.nullSafetyMode == NullSafetyMode.unsound)
'--no-sound-null-safety',
testFile,
],
environment,
);
} on Exception catch (error, stackTrace) {
if (error is ToolExit) {
rethrow;
}
throw Exception('Unable to run test: $error\n$stackTrace');
} finally {
await residentRunner?.exit();
await driver?.quit();
if (stringArg('write-sksl-on-exit') != null) {
final File outputFile = globals.fs.file(stringArg('write-sksl-on-exit'));
final vm_service.VmService vmService = await connectToVmService(
Uri.parse(observatoryUri),
);
final FlutterView flutterView = (await vmService.getFlutterViews()).first;
final Map<String, Object> result = await vmService.getSkSLs(
viewId: flutterView.id
);
await sharedSkSlWriter(_device, result, outputFile: outputFile);
}
if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) {
globals.printStatus('Leaving the application running.');
} else {
globals.printStatus('Stopping application instance.');
await appStopper(this, package);
}
await device?.dispose();
}
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
...@@ -238,28 +400,28 @@ class DriveCommand extends RunCommandBase { ...@@ -238,28 +400,28 @@ class DriveCommand extends RunCommandBase {
// If the --driver argument wasn't provided, then derive the value from // If the --driver argument wasn't provided, then derive the value from
// the target file. // the target file.
String appFile = _fileSystem.path.normalize(targetFile); String appFile = globals.fs.path.normalize(targetFile);
// This command extends `flutter run` and therefore CWD == package dir // This command extends `flutter run` and therefore CWD == package dir
final String packageDir = _fileSystem.currentDirectory.path; final String packageDir = globals.fs.currentDirectory.path;
// Make appFile path relative to package directory because we are looking // Make appFile path relative to package directory because we are looking
// for the corresponding test file relative to it. // for the corresponding test file relative to it.
if (!_fileSystem.path.isRelative(appFile)) { if (!globals.fs.path.isRelative(appFile)) {
if (!_fileSystem.path.isWithin(packageDir, appFile)) { if (!globals.fs.path.isWithin(packageDir, appFile)) {
_logger.printError( globals.printError(
'Application file $appFile is outside the package directory $packageDir' 'Application file $appFile is outside the package directory $packageDir'
); );
return null; return null;
} }
appFile = _fileSystem.path.relative(appFile, from: packageDir); appFile = globals.fs.path.relative(appFile, from: packageDir);
} }
final List<String> parts = _fileSystem.path.split(appFile); final List<String> parts = globals.fs.path.split(appFile);
if (parts.length < 2) { if (parts.length < 2) {
_logger.printError( globals.printError(
'Application file $appFile must reside in one of the sub-directories ' 'Application file $appFile must reside in one of the sub-directories '
'of the package structure, not in the root directory.' 'of the package structure, not in the root directory.'
); );
...@@ -269,8 +431,277 @@ class DriveCommand extends RunCommandBase { ...@@ -269,8 +431,277 @@ class DriveCommand extends RunCommandBase {
// Look for the test file inside `test_driver/` matching the sub-path, e.g. // Look for the test file inside `test_driver/` matching the sub-path, e.g.
// if the application is `lib/foo/bar.dart`, the test file is expected to // if the application is `lib/foo/bar.dart`, the test file is expected to
// be `test_driver/foo/bar_test.dart`. // be `test_driver/foo/bar_test.dart`.
final String pathWithNoExtension = _fileSystem.path.withoutExtension(_fileSystem.path.joinAll( final String pathWithNoExtension = globals.fs.path.withoutExtension(globals.fs.path.joinAll(
<String>[packageDir, 'test_driver', ...parts.skip(1)])); <String>[packageDir, 'test_driver', ...parts.skip(1)]));
return '${pathWithNoExtension}_test${_fileSystem.path.extension(appFile)}'; return '${pathWithNoExtension}_test${globals.fs.path.extension(appFile)}';
}
}
Future<Device> findTargetDevice({ @required Duration timeout }) async {
final DeviceManager deviceManager = globals.deviceManager;
final List<Device> devices = await deviceManager.findTargetDevices(null, timeout: timeout);
if (deviceManager.hasSpecifiedDeviceId) {
if (devices.isEmpty) {
globals.printStatus("No devices found with name or id matching '${deviceManager.specifiedDeviceId}'");
return null;
}
if (devices.length > 1) {
globals.printStatus("Found ${devices.length} devices with name or id matching '${deviceManager.specifiedDeviceId}':");
await Device.printDevices(devices, globals.logger);
return null;
}
return devices.first;
}
if (devices.isEmpty) {
globals.printError('No devices found.');
return null;
} else if (devices.length > 1) {
globals.printStatus('Found multiple connected devices:');
await Device.printDevices(devices, globals.logger);
}
globals.printStatus('Using device ${devices.first.name}.');
return devices.first;
}
/// Starts the application on the device given command configuration.
typedef AppStarter = Future<LaunchResult> Function(DriveCommand command, Uri webUri, ApplicationPackage applicationPackage, bool prebuiltApplication);
AppStarter appStarter = _startApp; // (mutable for testing)
void restoreAppStarter() {
appStarter = _startApp;
}
Future<LaunchResult> _startApp(
DriveCommand command,
Uri webUri,
ApplicationPackage applicationPackage,
bool prebuiltApplication,
) async {
final String mainPath = findMainDartFile(command.targetFile);
if (await globals.fs.type(mainPath) != FileSystemEntityType.file) {
globals.printError('Tried to run $mainPath, but that file does not exist.');
return null;
}
globals.printTrace('Stopping previously running application, if any.');
await appStopper(command, applicationPackage);
final Map<String, dynamic> platformArgs = <String, dynamic>{};
if (command.traceStartup) {
platformArgs['trace-startup'] = command.traceStartup;
}
if (webUri != null) {
platformArgs['uri'] = webUri.toString();
if (!command.getBuildInfo().isDebug) {
// For web device, startApp will be triggered twice
// and it will error out for chrome the second time.
platformArgs['no-launch-chrome'] = true;
}
}
globals.printTrace('Starting application.');
// Forward device log messages to the terminal window running the "drive" command.
final DeviceLogReader logReader = await command.device.getLogReader(app: applicationPackage);
command._deviceLogSubscription = logReader
.logLines
.listen(globals.printStatus);
final LaunchResult result = await command.device.startApp(
applicationPackage,
mainPath: mainPath,
route: command.route,
debuggingOptions: DebuggingOptions.enabled(
command.getBuildInfo(),
startPaused: true,
hostVmServicePort: webUri != null ? command.hostVmservicePort : 0,
disablePortPublication: command.disablePortPublication,
ddsPort: command.ddsPort,
verboseSystemLogs: command.verboseSystemLogs,
cacheSkSL: command.cacheSkSL,
dumpSkpOnShaderCompilation: command.dumpSkpOnShaderCompilation,
purgePersistentCache: command.purgePersistentCache,
),
platformArgs: platformArgs,
userIdentifier: command.userIdentifier,
prebuiltApplication: prebuiltApplication,
);
if (!result.started) {
await command._deviceLogSubscription.cancel();
return null;
}
return result;
}
/// Runs driver tests.
typedef TestRunner = Future<void> Function(List<String> testArgs, Map<String, String> environment);
TestRunner testRunner = _runTests;
void restoreTestRunner() {
testRunner = _runTests;
}
Future<void> _runTests(List<String> testArgs, Map<String, String> environment) async {
globals.printTrace('Running driver tests.');
globalPackagesPath = globals.fs.path.normalize(globals.fs.path.absolute(globalPackagesPath));
final int result = await globals.processUtils.stream(
<String>[
globals.artifacts.getArtifactPath(Artifact.engineDartBinary),
...testArgs,
'--packages=$globalPackagesPath',
'-rexpanded',
],
environment: environment,
);
if (result != 0) {
throwToolExit('Driver tests failed: $result', exitCode: result);
}
}
/// Stops the application.
typedef AppStopper = Future<bool> Function(DriveCommand command, ApplicationPackage applicationPackage);
AppStopper appStopper = _stopApp;
void restoreAppStopper() {
appStopper = _stopApp;
}
Future<bool> _stopApp(DriveCommand command, ApplicationPackage package) async {
globals.printTrace('Stopping application.');
final bool stopped = await command.device.stopApp(package, userIdentifier: command.userIdentifier);
await command.device.uninstallApp(package);
await command._deviceLogSubscription?.cancel();
return stopped;
}
/// A list of supported browsers.
@visibleForTesting
enum Browser {
/// Chrome on Android: https://developer.chrome.com/multidevice/android/overview
androidChrome,
/// Chrome: https://www.google.com/chrome/
chrome,
/// Edge: https://www.microsoft.com/en-us/windows/microsoft-edge
edge,
/// Firefox: https://www.mozilla.org/en-US/firefox/
firefox,
/// Safari in iOS: https://www.apple.com/safari/
iosSafari,
/// Safari in macOS: https://www.apple.com/safari/
safari,
}
/// Converts [browserName] string to [Browser]
Browser _browserNameToEnum(String browserName){
switch (browserName) {
case 'android-chrome': return Browser.androidChrome;
case 'chrome': return Browser.chrome;
case 'edge': return Browser.edge;
case 'firefox': return Browser.firefox;
case 'ios-safari': return Browser.iosSafari;
case 'safari': return Browser.safari;
}
throw UnsupportedError('Browser $browserName not supported');
}
Future<async_io.WebDriver> _createDriver(String driverPort, Browser browser, bool headless, String chromeBinary) async {
return async_io.createDriver(
uri: Uri.parse('http://localhost:$driverPort/'),
desired: getDesiredCapabilities(browser, headless, chromeBinary),
spec: async_io.WebDriverSpec.Auto
);
}
/// Returns desired capabilities for given [browser], [headless] and
/// [chromeBinary].
@visibleForTesting
Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless, [String chromeBinary]) {
switch (browser) {
case Browser.chrome:
return <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ async_io.LogType.performance: 'ALL'},
'chromeOptions': <String, dynamic>{
if (chromeBinary != null)
'binary': chromeBinary,
'w3c': false,
'args': <String>[
'--bwsi',
'--disable-background-timer-throttling',
'--disable-default-apps',
'--disable-extensions',
'--disable-popup-blocking',
'--disable-translate',
'--no-default-browser-check',
'--no-sandbox',
'--no-first-run',
if (headless) '--headless'
],
'perfLoggingPrefs': <String, String>{
'traceCategories':
'devtools.timeline,'
'v8,blink.console,benchmark,blink,'
'blink.user_timing'
}
},
};
break;
case Browser.firefox:
return <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'firefox',
'moz:firefoxOptions' : <String, dynamic>{
'args': <String>[
if (headless) '-headless'
],
'prefs': <String, dynamic>{
'dom.file.createInChild': true,
'dom.timeout.background_throttling_max_budget': -1,
'media.autoplay.default': 0,
'media.gmp-manager.url': '',
'media.gmp-provider.enabled': false,
'network.captive-portal-service.enabled': false,
'security.insecure_field_warning.contextual.enabled': false,
'test.currentTimeOffsetSeconds': 11491200
},
'log': <String, String>{'level': 'trace'}
}
};
break;
case Browser.edge:
return <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'edge',
};
break;
case Browser.safari:
return <String, dynamic>{
'browserName': 'safari',
};
break;
case Browser.iosSafari:
return <String, dynamic>{
'platformName': 'ios',
'browserName': 'safari',
'safari:useSimulator': true
};
case Browser.androidChrome:
return <String, dynamic>{
'browserName': 'chrome',
'platformName': 'android',
'goog:chromeOptions': <String, dynamic>{
'androidPackage': 'com.android.chrome',
'args': <String>['--disable-fullscreen']
},
};
default:
throw UnsupportedError('Browser $browser not supported.');
} }
} }
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart';
import '../android/android_device.dart'; import '../android/android_device.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
...@@ -23,8 +25,8 @@ import '../tracing.dart'; ...@@ -23,8 +25,8 @@ import '../tracing.dart';
import '../web/web_runner.dart'; import '../web/web_runner.dart';
import 'daemon.dart'; import 'daemon.dart';
/// Shared logic between `flutter run` and `flutter drive` commands.
abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopmentArtifacts { abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
// Used by run and drive commands.
RunCommandBase({ bool verboseHelp = false }) { RunCommandBase({ bool verboseHelp = false }) {
addBuildModeFlags(defaultToRelease: false, verboseHelp: verboseHelp); addBuildModeFlags(defaultToRelease: false, verboseHelp: verboseHelp);
usesDartDefineOption(); usesDartDefineOption();
...@@ -75,45 +77,6 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -75,45 +77,6 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
'this must be the path to an APK. For iOS applications, the path to an IPA. Other device types ' 'this must be the path to an APK. For iOS applications, the path to an IPA. Other device types '
'do not yet support prebuilt application binaries', 'do not yet support prebuilt application binaries',
valueHelp: 'path/to/app.apk', valueHelp: 'path/to/app.apk',
)
..addFlag('start-paused',
defaultsTo: startPausedDefault,
help: 'Start in a paused mode and wait for a debugger to connect.',
)
..addOption('dart-flags',
hide: !verboseHelp,
help: 'Pass a list of comma separated flags to the Dart instance at '
'application startup. Flags passed through this option must be '
'present on the allowlist defined within the Flutter engine. If '
'a disallowed flag is encountered, the process will be '
'terminated immediately.\n\n'
'This flag is not available on the stable channel and is only '
'applied in debug and profile modes. This option should only '
'be used for experiments and should not be used by typical users.'
)
..addFlag('endless-trace-buffer',
negatable: false,
help: 'Enable tracing to the endless tracer. This is useful when '
'recording huge amounts of traces. If we need to use endless buffer to '
'record startup traces, we can combine the ("--trace-startup"). '
'For example, flutter run --trace-startup --endless-trace-buffer. ',
)
..addFlag('trace-systrace',
negatable: false,
help: 'Enable tracing to the system tracer. This is only useful on '
'platforms where such a tracer is available (Android and Fuchsia).',
)
..addFlag('trace-skia',
negatable: false,
help: 'Enable tracing of Skia code. This is useful when debugging '
'the raster thread (formerly known as the GPU thread). '
'By default, Flutter will not log skia code.',
)
..addOption('trace-allowlist',
hide: true,
help: 'Filters out all trace events except those that are specified in '
'this comma separated list of allowed prefixes.',
valueHelp: 'foo,bar',
); );
usesWebOptions(hide: !verboseHelp); usesWebOptions(hide: !verboseHelp);
usesTargetOption(); usesTargetOption();
...@@ -133,71 +96,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -133,71 +96,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
bool get dumpSkpOnShaderCompilation => boolArg('dump-skp-on-shader-compilation'); bool get dumpSkpOnShaderCompilation => boolArg('dump-skp-on-shader-compilation');
bool get purgePersistentCache => boolArg('purge-persistent-cache'); bool get purgePersistentCache => boolArg('purge-persistent-cache');
bool get disableServiceAuthCodes => boolArg('disable-service-auth-codes'); bool get disableServiceAuthCodes => boolArg('disable-service-auth-codes');
bool get runningWithPrebuiltApplication => argResults['use-application-binary'] != null;
bool get trackWidgetCreation => boolArg('track-widget-creation');
/// Whether to start the application paused by default.
bool get startPausedDefault;
String get route => stringArg('route'); String get route => stringArg('route');
String get traceAllowlist => stringArg('trace-allowlist');
/// Create a debugging options instance for the current `run` or `drive` invocation.
DebuggingOptions createDebuggingOptions() {
final BuildInfo buildInfo = getBuildInfo();
final int browserDebugPort = featureFlags.isWebEnabled && argResults.wasParsed('web-browser-debug-port')
? int.parse(stringArg('web-browser-debug-port'))
: null;
if (buildInfo.mode.isRelease) {
return DebuggingOptions.disabled(
buildInfo,
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse',
webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse',
webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
webBrowserDebugPort: browserDebugPort,
);
} else {
return DebuggingOptions.enabled(
buildInfo,
startPaused: boolArg('start-paused'),
disableServiceAuthCodes: boolArg('disable-service-auth-codes'),
disableDds: boolArg('disable-dds'),
dartFlags: stringArg('dart-flags') ?? '',
useTestFonts: argParser.options.containsKey('use-test-fonts') && boolArg('use-test-fonts'),
enableSoftwareRendering: argParser.options.containsKey('enable-software-rendering') && boolArg('enable-software-rendering'),
skiaDeterministicRendering: argParser.options.containsKey('skia-deterministic-rendering') && boolArg('skia-deterministic-rendering'),
traceSkia: boolArg('trace-skia'),
traceAllowlist: traceAllowlist,
traceSystrace: boolArg('trace-systrace'),
endlessTraceBuffer: boolArg('endless-trace-buffer'),
dumpSkpOnShaderCompilation: dumpSkpOnShaderCompilation,
cacheSkSL: cacheSkSL,
purgePersistentCache: purgePersistentCache,
deviceVmServicePort: deviceVmservicePort,
hostVmServicePort: hostVmservicePort,
disablePortPublication: disablePortPublication,
ddsPort: ddsPort,
verboseSystemLogs: boolArg('verbose-system-logs'),
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse',
webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse',
webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
webBrowserDebugPort: browserDebugPort,
webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'),
vmserviceOutFile: stringArg('vmservice-out-file'),
fastStart: argParser.options.containsKey('fast-start')
&& boolArg('fast-start')
&& !runningWithPrebuiltApplication,
nullAssertions: boolArg('null-assertions'),
);
}
}
} }
class RunCommand extends RunCommandBase { class RunCommand extends RunCommandBase {
...@@ -212,6 +111,10 @@ class RunCommand extends RunCommandBase { ...@@ -212,6 +111,10 @@ class RunCommand extends RunCommandBase {
// without needing to know the port. // without needing to know the port.
addPublishPort(enabledByDefault: true, verboseHelp: verboseHelp); addPublishPort(enabledByDefault: true, verboseHelp: verboseHelp);
argParser argParser
..addFlag('start-paused',
negatable: false,
help: 'Start in a paused mode and wait for a debugger to connect.',
)
..addFlag('enable-software-rendering', ..addFlag('enable-software-rendering',
negatable: false, negatable: false,
help: 'Enable rendering using the Skia software backend. ' help: 'Enable rendering using the Skia software backend. '
...@@ -224,6 +127,35 @@ class RunCommand extends RunCommandBase { ...@@ -224,6 +127,35 @@ class RunCommand extends RunCommandBase {
help: 'When combined with --enable-software-rendering, provides 100% ' help: 'When combined with --enable-software-rendering, provides 100% '
'deterministic Skia rendering.', 'deterministic Skia rendering.',
) )
..addFlag('trace-skia',
negatable: false,
help: 'Enable tracing of Skia code. This is useful when debugging '
'the raster thread (formerly known as the GPU thread). '
'By default, Flutter will not log skia code.',
)
..addOption('trace-whitelist',
hide: true,
help: '(deprecated) Use --trace-allowlist instead',
valueHelp: 'foo,bar',
)
..addOption('trace-allowlist',
hide: true,
help: 'Filters out all trace events except those that are specified in '
'this comma separated list of allowed prefixes.',
valueHelp: 'foo,bar',
)
..addFlag('endless-trace-buffer',
negatable: false,
help: 'Enable tracing to the endless tracer. This is useful when '
'recording huge amounts of traces. If we need to use endless buffer to '
'record startup traces, we can combine the ("--trace-startup"). '
'For example, flutter run --trace-startup --endless-trace-buffer. ',
)
..addFlag('trace-systrace',
negatable: false,
help: 'Enable tracing to the system tracer. This is only useful on '
'platforms where such a tracer is available (Android and Fuchsia).',
)
..addFlag('await-first-frame-when-tracing', ..addFlag('await-first-frame-when-tracing',
defaultsTo: true, defaultsTo: true,
help: 'Whether to wait for the first frame when tracing startup ("--trace-startup"), ' help: 'Whether to wait for the first frame when tracing startup ("--trace-startup"), '
...@@ -244,6 +176,16 @@ class RunCommand extends RunCommandBase { ...@@ -244,6 +176,16 @@ class RunCommand extends RunCommandBase {
defaultsTo: true, defaultsTo: true,
help: 'If necessary, build the app before running.', help: 'If necessary, build the app before running.',
) )
..addOption('dart-flags',
hide: !verboseHelp,
help: 'Pass a list of comma separated flags to the Dart instance at '
'application startup. Flags passed through this option must be '
'present on the allowlist defined within the Flutter engine. If '
'a disallowed flag is encountered, the process will be '
'terminated immediately.\n\n'
'This flag is not available on the stable channel and is only '
'applied in debug and profile modes. This option should only '
'be used for experiments and should not be used by typical users.')
..addOption('project-root', ..addOption('project-root',
hide: !verboseHelp, hide: !verboseHelp,
help: 'Specify the project root directory.', help: 'Specify the project root directory.',
...@@ -306,9 +248,6 @@ class RunCommand extends RunCommandBase { ...@@ -306,9 +248,6 @@ class RunCommand extends RunCommandBase {
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser); String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
@override
bool get startPausedDefault => false;
@override @override
Future<String> get usagePath async { Future<String> get usagePath async {
final String command = await super.usagePath; final String command = await super.usagePath;
...@@ -403,6 +342,9 @@ class RunCommand extends RunCommandBase { ...@@ -403,6 +342,9 @@ class RunCommand extends RunCommandBase {
return getBuildInfo().isDebug && shouldUseHotMode; return getBuildInfo().isDebug && shouldUseHotMode;
} }
bool get runningWithPrebuiltApplication =>
argResults['use-application-binary'] != null;
bool get stayResident => boolArg('resident'); bool get stayResident => boolArg('resident');
bool get awaitFirstFrameWhenTracing => boolArg('await-first-frame-when-tracing'); bool get awaitFirstFrameWhenTracing => boolArg('await-first-frame-when-tracing');
...@@ -430,6 +372,73 @@ class RunCommand extends RunCommandBase { ...@@ -430,6 +372,73 @@ class RunCommand extends RunCommandBase {
} }
} }
String get _traceAllowlist {
final String deprecatedValue = stringArg('trace-whitelist');
if (deprecatedValue != null) {
globals.printError('--trace-whitelist has been deprecated, use --trace-allowlist instead');
}
return stringArg('trace-allowlist') ?? deprecatedValue;
}
DebuggingOptions _createDebuggingOptions() {
final BuildInfo buildInfo = getBuildInfo();
final int browserDebugPort = featureFlags.isWebEnabled && argResults.wasParsed('web-browser-debug-port')
? int.parse(stringArg('web-browser-debug-port'))
: null;
if (buildInfo.mode.isRelease) {
return DebuggingOptions.disabled(
buildInfo,
initializePlatform: boolArg('web-initialize-platform'),
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse',
webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse',
webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
webBrowserDebugPort: browserDebugPort,
);
} else {
return DebuggingOptions.enabled(
buildInfo,
startPaused: boolArg('start-paused'),
disableServiceAuthCodes: boolArg('disable-service-auth-codes'),
disableDds: boolArg('disable-dds'),
dartFlags: stringArg('dart-flags') ?? '',
useTestFonts: boolArg('use-test-fonts'),
enableSoftwareRendering: boolArg('enable-software-rendering'),
skiaDeterministicRendering: boolArg('skia-deterministic-rendering'),
traceSkia: boolArg('trace-skia'),
traceAllowlist: _traceAllowlist,
traceSystrace: boolArg('trace-systrace'),
endlessTraceBuffer: boolArg('endless-trace-buffer'),
dumpSkpOnShaderCompilation: dumpSkpOnShaderCompilation,
cacheSkSL: cacheSkSL,
purgePersistentCache: purgePersistentCache,
deviceVmServicePort: deviceVmservicePort,
hostVmServicePort: hostVmservicePort,
disablePortPublication: disablePortPublication,
ddsPort: ddsPort,
verboseSystemLogs: boolArg('verbose-system-logs'),
initializePlatform: boolArg('web-initialize-platform'),
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse',
webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse',
webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
webBrowserDebugPort: browserDebugPort,
webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'),
vmserviceOutFile: stringArg('vmservice-out-file'),
// Allow forcing fast-start to off to prevent doing more work on devices that
// don't support it.
fastStart: boolArg('fast-start')
&& !runningWithPrebuiltApplication
&& devices.every((Device device) => device.supportsFastStart),
nullAssertions: boolArg('null-assertions'),
);
}
}
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
// Enable hot mode by default if `--no-hot` was not passed and we are in // Enable hot mode by default if `--no-hot` was not passed and we are in
...@@ -455,11 +464,11 @@ class RunCommand extends RunCommandBase { ...@@ -455,11 +464,11 @@ class RunCommand extends RunCommandBase {
final String applicationBinaryPath = stringArg('use-application-binary'); final String applicationBinaryPath = stringArg('use-application-binary');
app = await daemon.appDomain.startApp( app = await daemon.appDomain.startApp(
devices.first, globals.fs.currentDirectory.path, targetFile, route, devices.first, globals.fs.currentDirectory.path, targetFile, route,
createDebuggingOptions(), hotMode, _createDebuggingOptions(), hotMode,
applicationBinary: applicationBinaryPath == null applicationBinary: applicationBinaryPath == null
? null ? null
: globals.fs.file(applicationBinaryPath), : globals.fs.file(applicationBinaryPath),
trackWidgetCreation: trackWidgetCreation, trackWidgetCreation: boolArg('track-widget-creation'),
projectRootPath: stringArg('project-root'), projectRootPath: stringArg('project-root'),
packagesFilePath: globalResults['packages'] as String, packagesFilePath: globalResults['packages'] as String,
dillOutputPath: stringArg('output-dill'), dillOutputPath: stringArg('output-dill'),
...@@ -482,6 +491,11 @@ class RunCommand extends RunCommandBase { ...@@ -482,6 +491,11 @@ class RunCommand extends RunCommandBase {
} }
globals.terminal.usesTerminalUi = true; globals.terminal.usesTerminalUi = true;
if (argResults['dart-flags'] != null && !globals.flutterVersion.isMaster) {
throw UsageException('--dart-flags is not available on the stable '
'channel.', null);
}
final BuildMode buildMode = getBuildMode(); final BuildMode buildMode = getBuildMode();
for (final Device device in devices) { for (final Device device in devices) {
if (!await device.supportsRuntimeMode(buildMode)) { if (!await device.supportsRuntimeMode(buildMode)) {
...@@ -520,6 +534,7 @@ class RunCommand extends RunCommandBase { ...@@ -520,6 +534,7 @@ class RunCommand extends RunCommandBase {
for (final Device device in devices) for (final Device device in devices)
await FlutterDevice.create( await FlutterDevice.create(
device, device,
flutterProject: flutterProject,
fileSystemRoots: stringsArg('filesystem-root'), fileSystemRoots: stringsArg('filesystem-root'),
fileSystemScheme: stringArg('filesystem-scheme'), fileSystemScheme: stringArg('filesystem-scheme'),
experimentalFlags: expFlags, experimentalFlags: expFlags,
...@@ -541,7 +556,7 @@ class RunCommand extends RunCommandBase { ...@@ -541,7 +556,7 @@ class RunCommand extends RunCommandBase {
runner = HotRunner( runner = HotRunner(
flutterDevices, flutterDevices,
target: targetFile, target: targetFile,
debuggingOptions: createDebuggingOptions(), debuggingOptions: _createDebuggingOptions(),
benchmarkMode: boolArg('benchmark'), benchmarkMode: boolArg('benchmark'),
applicationBinary: applicationBinaryPath == null applicationBinary: applicationBinaryPath == null
? null ? null
...@@ -557,7 +572,7 @@ class RunCommand extends RunCommandBase { ...@@ -557,7 +572,7 @@ class RunCommand extends RunCommandBase {
target: targetFile, target: targetFile,
flutterProject: flutterProject, flutterProject: flutterProject,
ipv6: ipv6, ipv6: ipv6,
debuggingOptions: createDebuggingOptions(), debuggingOptions: _createDebuggingOptions(),
stayResident: stayResident, stayResident: stayResident,
urlTunneller: null, urlTunneller: null,
); );
...@@ -565,7 +580,7 @@ class RunCommand extends RunCommandBase { ...@@ -565,7 +580,7 @@ class RunCommand extends RunCommandBase {
runner = ColdRunner( runner = ColdRunner(
flutterDevices, flutterDevices,
target: targetFile, target: targetFile,
debuggingOptions: createDebuggingOptions(), debuggingOptions: _createDebuggingOptions(),
traceStartup: traceStartup, traceStartup: traceStartup,
awaitFirstFrameWhenTracing: awaitFirstFrameWhenTracing, awaitFirstFrameWhenTracing: awaitFirstFrameWhenTracing,
applicationBinary: applicationBinaryPath == null applicationBinary: applicationBinaryPath == null
......
...@@ -847,6 +847,7 @@ class DebuggingOptions { ...@@ -847,6 +847,7 @@ class DebuggingOptions {
this.disablePortPublication = false, this.disablePortPublication = false,
this.deviceVmServicePort, this.deviceVmServicePort,
this.ddsPort, this.ddsPort,
this.initializePlatform = true,
this.hostname, this.hostname,
this.port, this.port,
this.webEnableExposeUrl, this.webEnableExposeUrl,
...@@ -861,6 +862,7 @@ class DebuggingOptions { ...@@ -861,6 +862,7 @@ class DebuggingOptions {
}) : debuggingEnabled = true; }) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildInfo, { DebuggingOptions.disabled(this.buildInfo, {
this.initializePlatform = true,
this.port, this.port,
this.hostname, this.hostname,
this.webEnableExposeUrl, this.webEnableExposeUrl,
...@@ -911,6 +913,8 @@ class DebuggingOptions { ...@@ -911,6 +913,8 @@ class DebuggingOptions {
final bool purgePersistentCache; final bool purgePersistentCache;
final bool useTestFonts; final bool useTestFonts;
final bool verboseSystemLogs; final bool verboseSystemLogs;
/// Whether to invoke webOnlyInitializePlatform in Flutter for web.
final bool initializePlatform;
final int hostVmServicePort; final int hostVmServicePort;
final int deviceVmServicePort; final int deviceVmServicePort;
final bool disablePortPublication; final bool disablePortPublication;
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:dds/dds.dart' as dds;
import 'package:file/file.dart';
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import '../application_package.dart';
import '../base/common.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../device.dart';
import '../vmservice.dart';
import 'web_driver_service.dart';
class FlutterDriverFactory {
FlutterDriverFactory({
@required ApplicationPackageFactory applicationPackageFactory,
@required Logger logger,
@required ProcessUtils processUtils,
@required String dartSdkPath,
}) : _applicationPackageFactory = applicationPackageFactory,
_logger = logger,
_processUtils = processUtils,
_dartSdkPath = dartSdkPath;
final ApplicationPackageFactory _applicationPackageFactory;
final Logger _logger;
final ProcessUtils _processUtils;
final String _dartSdkPath;
/// Create a driver service for running `flutter drive`.
DriverService createDriverService(bool web) {
if (web) {
return WebDriverService(
processUtils: _processUtils,
dartSdkPath: _dartSdkPath,
);
}
return FlutterDriverService(
logger: _logger,
processUtils: _processUtils,
dartSdkPath: _dartSdkPath,
applicationPackageFactory: _applicationPackageFactory,
);
}
}
/// An interface for the `flutter driver` integration test operations.
abstract class DriverService {
/// Install and launch the application for the provided [device].
Future<void> start(
BuildInfo buildInfo,
Device device,
DebuggingOptions debuggingOptions,
bool ipv6, {
File applicationBinary,
String route,
String userIdentifier,
String mainPath,
Map<String, Object> platformArgs = const <String, Object>{},
});
/// Start the test file with the provided [arguments] and [environment], returning
/// the test process exit code.
Future<int> startTest(
String testFile,
List<String> arguments,
Map<String, String> environment, {
bool headless,
String chromeBinary,
String browserName,
bool androidEmulator,
int driverPort,
List<String> browserDimension,
});
/// Stop the running application and uninstall it from the device.
///
/// If [writeSkslOnExit] is non-null, will connect to the VM Service
/// and write SkSL to the file. This is only supported on mobile and
/// desktop devices.
Future<void> stop({
File writeSkslOnExit,
String userIdentifier,
});
}
/// An implementation of the driver service that connects to mobile and desktop
/// applications.
class FlutterDriverService extends DriverService {
FlutterDriverService({
@required ApplicationPackageFactory applicationPackageFactory,
@required Logger logger,
@required ProcessUtils processUtils,
@required String dartSdkPath,
@visibleForTesting VMServiceConnector vmServiceConnector = connectToVmService,
}) : _applicationPackageFactory = applicationPackageFactory,
_logger = logger,
_processUtils = processUtils,
_dartSdkPath = dartSdkPath,
_vmServiceConnector = vmServiceConnector;
static const int _kLaunchAttempts = 3;
final ApplicationPackageFactory _applicationPackageFactory;
final Logger _logger;
final ProcessUtils _processUtils;
final String _dartSdkPath;
final VMServiceConnector _vmServiceConnector;
Device _device;
ApplicationPackage _applicationPackage;
String _vmServiceUri;
vm_service.VmService _vmService;
@override
Future<void> start(
BuildInfo buildInfo,
Device device,
DebuggingOptions debuggingOptions,
bool ipv6, {
File applicationBinary,
String route,
String userIdentifier,
Map<String, Object> platformArgs = const <String, Object>{},
String mainPath,
}) async {
if (buildInfo.isRelease) {
throwToolExit(
'Flutter Driver (non-web) does not support running in release mode.\n'
'\n'
'Use --profile mode for testing application performance.\n'
'Use --debug (default) mode for testing correctness (with assertions).'
);
}
_device = device;
final TargetPlatform targetPlatform = await device.targetPlatform;
_applicationPackage = await _applicationPackageFactory.getPackageForPlatform(
targetPlatform,
buildInfo: buildInfo,
applicationBinary: applicationBinary,
);
int attempt = 0;
LaunchResult result;
bool prebuiltApplication = applicationBinary != null;
while (attempt < _kLaunchAttempts) {
result = await device.startApp(
_applicationPackage,
mainPath: mainPath,
route: route,
debuggingOptions: debuggingOptions,
platformArgs: platformArgs,
userIdentifier: userIdentifier,
prebuiltApplication: prebuiltApplication,
);
if (result != null && result.started) {
break;
}
// On attempts past 1, assume the application is built correctly and re-use it.
attempt += 1;
prebuiltApplication = true;
_logger.printError('Application failed to start on attempt: $attempt');
}
if (result == null || !result.started) {
throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1);
}
_vmServiceUri = result.observatoryUri.toString();
try {
await device.dds.startDartDevelopmentService(
result.observatoryUri,
debuggingOptions.ddsPort,
ipv6,
debuggingOptions.disableServiceAuthCodes,
);
_vmServiceUri = device.dds.uri.toString();
} on dds.DartDevelopmentServiceException {
// If there's another flutter_tools instance still connected to the target
// application, DDS will already be running remotely and this call will fail.
// This can be ignored to continue to use the existing remote DDS instance.
}
_vmService = await _vmServiceConnector(Uri.parse(_vmServiceUri), device: _device);
final DeviceLogReader logReader = await device.getLogReader(app: _applicationPackage);
logReader.logLines.listen(_logger.printStatus);
final vm_service.VM vm = await _vmService.getVM();
logReader.appPid = vm.pid;
}
@override
Future<int> startTest(
String testFile,
List<String> arguments,
Map<String, String> environment, {
bool headless,
String chromeBinary,
String browserName,
bool androidEmulator,
int driverPort,
List<String> browserDimension,
}) async {
return _processUtils.stream(<String>[
_dartSdkPath,
...arguments,
testFile,
'-rexpanded',
], environment: <String, String>{
'VM_SERVICE_URL': _vmServiceUri,
...environment,
});
}
@override
Future<void> stop({
File writeSkslOnExit,
String userIdentifier,
}) async {
if (writeSkslOnExit != null) {
final FlutterView flutterView = (await _vmService.getFlutterViews()).first;
final Map<String, Object> result = await _vmService.getSkSLs(
viewId: flutterView.id
);
await sharedSkSlWriter(_device, result, outputFile: writeSkslOnExit, logger: _logger);
}
try {
if (!await _device.stopApp(_applicationPackage, userIdentifier: userIdentifier)) {
_logger.printError('Failed to stop app');
}
} on Exception catch (err) {
_logger.printError('Failed to stop app due to unhandled error: $err');
}
try {
if (!await _device.uninstallApp(_applicationPackage, userIdentifier: userIdentifier)) {
_logger.printError('Failed to uninstall app');
}
} on Exception catch (err) {
_logger.printError('Failed to uninstall app due to unhandled error: $err');
}
await _device.dispose();
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:math' as math;
import 'package:file/file.dart';
import 'package:meta/meta.dart';
import 'package:webdriver/async_io.dart' as async_io;
import '../base/common.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../convert.dart';
import '../device.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../resident_runner.dart';
import '../web/web_runner.dart';
import 'drive_service.dart';
/// An implementation of the driver service for web debug and release applications.
class WebDriverService extends DriverService {
WebDriverService({
@required ProcessUtils processUtils,
@required String dartSdkPath,
}) : _processUtils = processUtils,
_dartSdkPath = dartSdkPath;
final ProcessUtils _processUtils;
final String _dartSdkPath;
ResidentRunner _residentRunner;
Uri _webUri;
@override
Future<void> start(
BuildInfo buildInfo,
Device device,
DebuggingOptions debuggingOptions,
bool ipv6, {
File applicationBinary,
String route,
String userIdentifier,
String mainPath,
Map<String, Object> platformArgs = const <String, Object>{},
}) async {
final FlutterDevice flutterDevice = await FlutterDevice.create(
device,
target: mainPath,
buildInfo: buildInfo,
platform: globals.platform,
);
_residentRunner = webRunnerFactory.createWebRunner(
flutterDevice,
target: mainPath,
ipv6: ipv6,
debuggingOptions: debuggingOptions,
stayResident: false,
urlTunneller: null,
flutterProject: FlutterProject.current(),
);
final Completer<void> appStartedCompleter = Completer<void>.sync();
final int result = await _residentRunner.run(
appStartedCompleter: appStartedCompleter,
route: route,
);
_webUri = _residentRunner.uri;
if (result != 0) {
throwToolExit(null);
}
}
@override
Future<int> startTest(String testFile, List<String> arguments, Map<String, String> environment, {
bool headless,
String chromeBinary,
String browserName,
bool androidEmulator,
int driverPort,
List<String> browserDimension,
}) async {
async_io.WebDriver webDriver;
final Browser browser = _browserNameToEnum(browserName);
try {
webDriver = await async_io.createDriver(
uri: Uri.parse('http://localhost:$driverPort/'),
desired: getDesiredCapabilities(browser, headless, chromeBinary),
spec: async_io.WebDriverSpec.Auto
);
} on Exception catch (ex) {
throwToolExit(
'Unable to start WebDriver Session for Flutter for Web testing. \n'
'Make sure you have the correct WebDriver Server running at $driverPort. \n'
'Make sure the WebDriver Server matches option --browser-name. \n'
'$ex'
);
}
final bool isAndroidChrome = browser == Browser.androidChrome;
// Do not set the window size for android chrome browser.
if (!isAndroidChrome) {
assert(browserDimension.length == 2);
int x;
int y;
try {
x = int.parse(browserDimension[0]);
y = int.parse(browserDimension[1]);
} on FormatException catch (ex) {
throwToolExit('Dimension provided to --browser-dimension is invalid: $ex');
}
final async_io.Window window = await webDriver.window;
await window.setLocation(const math.Point<int>(0, 0));
await window.setSize(math.Rectangle<int>(0, 0, x, y));
}
final int result = await _processUtils.stream(<String>[
_dartSdkPath,
...arguments,
testFile,
'-rexpanded',
], environment: <String, String>{
'VM_SERVICE_URL': _webUri.toString(),
..._additionalDriverEnvironment(webDriver, browserName, androidEmulator),
...environment,
});
await webDriver.quit();
return result;
}
@override
Future<void> stop({File writeSkslOnExit, String userIdentifier}) async {
await _residentRunner.cleanupAtFinish();
}
Map<String, String> _additionalDriverEnvironment(async_io.WebDriver webDriver, String browserName, bool androidEmulator) {
return <String, String>{
'DRIVER_SESSION_ID': webDriver.id,
'DRIVER_SESSION_URI': webDriver.uri.toString(),
'DRIVER_SESSION_SPEC': webDriver.spec.toString(),
'DRIVER_SESSION_CAPABILITIES': json.encode(webDriver.capabilities),
'SUPPORT_TIMELINE_ACTION': (_browserNameToEnum(browserName) == Browser.chrome).toString(),
'FLUTTER_WEB_TEST': 'true',
'ANDROID_CHROME_ON_EMULATOR': (_browserNameToEnum(browserName) == Browser.androidChrome && androidEmulator).toString(),
};
}
}
/// A list of supported browsers.
enum Browser {
/// Chrome on Android: https://developer.chrome.com/multidevice/android/overview
androidChrome,
/// Chrome: https://www.google.com/chrome/
chrome,
/// Edge: https://www.microsoft.com/en-us/windows/microsoft-edge
edge,
/// Firefox: https://www.mozilla.org/en-US/firefox/
firefox,
/// Safari in iOS: https://www.apple.com/safari/
iosSafari,
/// Safari in macOS: https://www.apple.com/safari/
safari,
}
/// Returns desired capabilities for given [browser], [headless] and
/// [chromeBinary].
@visibleForTesting
Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless, [String chromeBinary]) {
switch (browser) {
case Browser.chrome:
return <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ async_io.LogType.performance: 'ALL'},
'chromeOptions': <String, dynamic>{
if (chromeBinary != null)
'binary': chromeBinary,
'w3c': false,
'args': <String>[
'--bwsi',
'--disable-background-timer-throttling',
'--disable-default-apps',
'--disable-extensions',
'--disable-popup-blocking',
'--disable-translate',
'--no-default-browser-check',
'--no-sandbox',
'--no-first-run',
if (headless) '--headless'
],
'perfLoggingPrefs': <String, String>{
'traceCategories':
'devtools.timeline,'
'v8,blink.console,benchmark,blink,'
'blink.user_timing'
}
},
};
break;
case Browser.firefox:
return <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'firefox',
'moz:firefoxOptions' : <String, dynamic>{
'args': <String>[
if (headless) '-headless'
],
'prefs': <String, dynamic>{
'dom.file.createInChild': true,
'dom.timeout.background_throttling_max_budget': -1,
'media.autoplay.default': 0,
'media.gmp-manager.url': '',
'media.gmp-provider.enabled': false,
'network.captive-portal-service.enabled': false,
'security.insecure_field_warning.contextual.enabled': false,
'test.currentTimeOffsetSeconds': 11491200
},
'log': <String, String>{'level': 'trace'}
}
};
break;
case Browser.edge:
return <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'edge',
};
break;
case Browser.safari:
return <String, dynamic>{
'browserName': 'safari',
};
break;
case Browser.iosSafari:
return <String, dynamic>{
'platformName': 'ios',
'browserName': 'safari',
'safari:useSimulator': true
};
case Browser.androidChrome:
return <String, dynamic>{
'browserName': 'chrome',
'platformName': 'android',
'goog:chromeOptions': <String, dynamic>{
'androidPackage': 'com.android.chrome',
'args': <String>['--disable-fullscreen']
},
};
default:
throw UnsupportedError('Browser $browser not supported.');
}
}
/// Converts [browserName] string to [Browser]
Browser _browserNameToEnum(String browserName){
switch (browserName) {
case 'android-chrome': return Browser.androidChrome;
case 'chrome': return Browser.chrome;
case 'edge': return Browser.edge;
case 'firefox': return Browser.firefox;
case 'ios-safari': return Browser.iosSafari;
case 'safari': return Browser.safari;
}
throw UnsupportedError('Browser $browserName not supported');
}
...@@ -528,6 +528,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -528,6 +528,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
flutterProject, flutterProject,
target, target,
debuggingOptions.buildInfo, debuggingOptions.buildInfo,
debuggingOptions.initializePlatform,
false, false,
kNoneWorker, kNoneWorker,
true, true,
...@@ -595,6 +596,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -595,6 +596,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
flutterProject, flutterProject,
target, target,
debuggingOptions.buildInfo, debuggingOptions.buildInfo,
debuggingOptions.initializePlatform,
false, false,
kNoneWorker, kNoneWorker,
true, true,
......
...@@ -70,6 +70,7 @@ class FlutterDevice { ...@@ -70,6 +70,7 @@ class FlutterDevice {
/// Create a [FlutterDevice] with optional code generation enabled. /// Create a [FlutterDevice] with optional code generation enabled.
static Future<FlutterDevice> create( static Future<FlutterDevice> create(
Device device, { Device device, {
@required FlutterProject flutterProject,
@required String target, @required String target,
@required BuildInfo buildInfo, @required BuildInfo buildInfo,
@required Platform platform, @required Platform platform,
......
...@@ -8,7 +8,6 @@ import 'package:vm_service/vm_service.dart' as vm_service; ...@@ -8,7 +8,6 @@ import 'package:vm_service/vm_service.dart' as vm_service;
import 'base/context.dart'; import 'base/context.dart';
import 'base/io.dart' as io; import 'base/io.dart' as io;
import 'base/logger.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'convert.dart'; import 'convert.dart';
import 'device.dart'; import 'device.dart';
...@@ -807,11 +806,9 @@ bool isPauseEvent(String kind) { ...@@ -807,11 +806,9 @@ bool isPauseEvent(String kind) {
// or delete it. // or delete it.
Future<String> sharedSkSlWriter(Device device, Map<String, Object> data, { Future<String> sharedSkSlWriter(Device device, Map<String, Object> data, {
File outputFile, File outputFile,
Logger logger,
}) async { }) async {
logger ??= globals.logger;
if (data.isEmpty) { if (data.isEmpty) {
logger.printStatus( globals.logger.printStatus(
'No data was received. To ensure SkSL data can be generated use a ' 'No data was received. To ensure SkSL data can be generated use a '
'physical device then:\n' 'physical device then:\n'
' 1. Pass "--cache-sksl" as an argument to flutter run.\n' ' 1. Pass "--cache-sksl" as an argument to flutter run.\n'
...@@ -847,7 +844,7 @@ Future<String> sharedSkSlWriter(Device device, Map<String, Object> data, { ...@@ -847,7 +844,7 @@ Future<String> sharedSkSlWriter(Device device, Map<String, Object> data, {
'data': data, 'data': data,
}; };
outputFile.writeAsStringSync(json.encode(manifest)); outputFile.writeAsStringSync(json.encode(manifest));
logger.printStatus('Wrote SkSL data to ${outputFile.path}.'); globals.logger.printStatus('Wrote SkSL data to ${outputFile.path}.');
return outputFile.path; return outputFile.path;
} }
......
...@@ -26,6 +26,7 @@ Future<void> buildWeb( ...@@ -26,6 +26,7 @@ Future<void> buildWeb(
FlutterProject flutterProject, FlutterProject flutterProject,
String target, String target,
BuildInfo buildInfo, BuildInfo buildInfo,
bool initializePlatform,
bool csp, bool csp,
String serviceWorkerStrategy, String serviceWorkerStrategy,
bool sourceMaps, bool sourceMaps,
...@@ -53,6 +54,7 @@ Future<void> buildWeb( ...@@ -53,6 +54,7 @@ Future<void> buildWeb(
defines: <String, String>{ defines: <String, String>{
kBuildMode: getNameForBuildMode(buildInfo.mode), kBuildMode: getNameForBuildMode(buildInfo.mode),
kTargetFile: target, kTargetFile: target,
kInitializePlatform: initializePlatform.toString(),
kHasWebPlugins: hasWebPlugins.toString(), kHasWebPlugins: hasWebPlugins.toString(),
kDartDefines: encodeDartDefines(buildInfo.dartDefines), kDartDefines: encodeDartDefines(buildInfo.dartDefines),
kCspMode: csp.toString(), kCspMode: csp.toString(),
......
...@@ -54,6 +54,7 @@ void main() { ...@@ -54,6 +54,7 @@ void main() {
fileSystem.path.join('lib', 'main.dart'), fileSystem.path.join('lib', 'main.dart'),
BuildInfo.debug, BuildInfo.debug,
false, false,
false,
null, null,
true, true,
), throwsToolExit()); ), throwsToolExit());
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/dds.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/drive.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart';
import 'package:webdriver/sync_io.dart' as sync_io;
import 'package:flutter_tools/src/vmservice.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fakes.dart';
void main() {
group('drive', () {
DriveCommand command;
Device mockUnsupportedDevice;
MemoryFileSystem fs;
Directory tempDir;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
command = DriveCommand();
fs = MemoryFileSystem.test();
tempDir = fs.systemTempDirectory.createTempSync('flutter_drive_test.');
fs.currentDirectory = tempDir;
fs.directory('test').createSync();
fs.directory('test_driver').createSync();
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
setExitFunctionForTests();
appStarter = (DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) {
throw 'Unexpected call to appStarter';
};
testRunner = (List<String> testArgs, Map<String, String> environment) {
throw 'Unexpected call to testRunner';
};
appStopper = (DriveCommand command, ApplicationPackage package) {
throw 'Unexpected call to appStopper';
};
command.applicationPackages = FakeApplicationPackageFactory();
});
tearDown(() {
command = null;
restoreExitFunction();
restoreAppStarter();
restoreAppStopper();
restoreTestRunner();
tryToDelete(tempDir);
});
void applyDdsMocks(Device device) {
final MockDartDevelopmentService mockDds = MockDartDevelopmentService();
when(device.dds).thenReturn(mockDds);
when(mockDds.startDartDevelopmentService(any, any, any, any)).thenReturn(null);
when(mockDds.uri).thenReturn(Uri.parse('http://localhost:8181'));
}
testUsingContext('returns 1 when test file is not found', () async {
testDeviceManager.addDevice(MockDevice());
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
globals.fs.file(testApp).createSync(recursive: true);
final List<String> args = <String>[
'drive',
'--target=$testApp',
'--no-pub',
];
try {
await createTestCommandRunner(command).run(args);
fail('Expect exception');
} on ToolExit catch (e) {
expect(e.exitCode ?? 1, 1);
expect(e.message, contains('Test file not found: $testFile'));
}
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns 1 when app fails to run', () async {
testDeviceManager.addDevice(MockDevice());
appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async => null, count: 3);
final String testApp = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e.dart');
final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
final MemoryFileSystem memFs = fs;
await memFs.file(testApp).writeAsString('main() { }');
await memFs.file(testFile).writeAsString('main() { }');
final List<String> args = <String>[
'drive',
'--target=$testApp',
'--no-pub',
];
try {
await createTestCommandRunner(command).run(args);
fail('Expect exception');
} on ToolExit catch (e) {
expect(e.exitCode, 1);
expect(e.message, contains('Application failed to start. Will not run test. Quitting.'));
}
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns 1 when app file is outside package', () async {
final String appFile = globals.fs.path.join(tempDir.dirname, 'other_app', 'app.dart');
globals.fs.file(appFile).createSync(recursive: true);
final List<String> args = <String>[
'--no-wrap',
'drive',
'--target=$appFile',
'--no-pub',
];
try {
await createTestCommandRunner(command).run(args);
fail('Expect exception');
} on ToolExit catch (e) {
expect(e.exitCode ?? 1, 1);
expect(testLogger.errorText, contains(
'Application file $appFile is outside the package directory ${tempDir.path}',
));
}
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns 1 when app file is in the root dir', () async {
final String appFile = globals.fs.path.join(tempDir.path, 'main.dart');
globals.fs.file(appFile).createSync(recursive: true);
final List<String> args = <String>[
'--no-wrap',
'drive',
'--target=$appFile',
'--no-pub',
];
try {
await createTestCommandRunner(command).run(args);
fail('Expect exception');
} on ToolExit catch (e) {
expect(e.exitCode ?? 1, 1);
expect(testLogger.errorText, contains(
'Application file main.dart must reside in one of the '
'sub-directories of the package structure, not in the root directory.',
));
}
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns 1 when targeted device is not Android with --device-user', () async {
testDeviceManager.addDevice(MockDevice());
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
globals.fs.file(testApp).createSync(recursive: true);
final List<String> args = <String>[
'drive',
'--target=$testApp',
'--no-pub',
'--device-user',
'10',
];
expect(() async => await createTestCommandRunner(command).run(args),
throwsToolExit(message: '--device-user is only supported for Android'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns 0 when test ends successfully', () async {
final MockAndroidDevice mockDevice = MockAndroidDevice();
applyDdsMocks(mockDevice);
testDeviceManager.addDevice(mockDevice);
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async {
return LaunchResult.succeeded();
});
testRunner = expectAsync2((List<String> testArgs, Map<String, String> environment) async {
expect(testArgs, <String>['--no-sound-null-safety', testFile]);
// VM_SERVICE_URL is not set by drive command arguments
expect(environment, <String, String>{
'VM_SERVICE_URL': 'null',
});
});
appStopper = expectAsync2((DriveCommand command, ApplicationPackage package) async {
return true;
});
final MemoryFileSystem memFs = fs;
await memFs.file(testApp).writeAsString('main() {}');
await memFs.file(testFile).writeAsString('main() {}');
final List<String> args = <String>[
'drive',
'--target=$testApp',
'--no-pub',
'--disable-dds',
'--device-user',
'10',
];
await createTestCommandRunner(command).run(args);
verify(mockDevice.dispose());
expect(testLogger.errorText, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns exitCode set by test runner', () async {
final MockDevice mockDevice = MockDevice();
applyDdsMocks(mockDevice);
testDeviceManager.addDevice(mockDevice);
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async {
return LaunchResult.succeeded();
});
testRunner = (List<String> testArgs, Map<String, String> environment) async {
throwToolExit(null, exitCode: 123);
};
appStopper = expectAsync2((DriveCommand command, ApplicationPackage package) async {
return true;
});
final MemoryFileSystem memFs = fs;
await memFs.file(testApp).writeAsString('main() {}');
await memFs.file(testFile).writeAsString('main() {}');
final List<String> args = <String>[
'drive',
'--target=$testApp',
'--no-pub',
];
try {
await createTestCommandRunner(command).run(args);
fail('Expect exception');
} on ToolExit catch (e) {
expect(e.exitCode ?? 1, 123);
expect(e.message, isNull);
}
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('enable experiment', () async {
final MockAndroidDevice mockDevice = MockAndroidDevice();
applyDdsMocks(mockDevice);
testDeviceManager.addDevice(mockDevice);
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async {
return LaunchResult.succeeded();
});
testRunner = expectAsync2((List<String> testArgs, Map<String, String> environment) async {
expect(
testArgs,
<String>[
'--enable-experiment=experiment1,experiment2',
'--no-sound-null-safety',
testFile,
]
);
});
appStopper = expectAsync2((DriveCommand command, ApplicationPackage package) async {
return true;
});
final MemoryFileSystem memFs = fs;
await memFs.file(testApp).writeAsString('main() {}');
await memFs.file(testFile).writeAsString('main() {}');
final List<String> args = <String>[
'drive',
'--target=$testApp',
'--no-pub',
'--enable-experiment=experiment1',
'--enable-experiment=experiment2',
];
await createTestCommandRunner(command).run(args);
expect(testLogger.errorText, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('sound null safety', () async {
final MockAndroidDevice mockDevice = MockAndroidDevice();
applyDdsMocks(mockDevice);
testDeviceManager.addDevice(mockDevice);
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async {
return LaunchResult.succeeded();
});
testRunner = expectAsync2((List<String> testArgs, Map<String, String> environment) async {
expect(
testArgs,
<String>[
'--sound-null-safety',
testFile,
]
);
});
appStopper = expectAsync2((DriveCommand command, ApplicationPackage package) async {
return true;
});
final MemoryFileSystem memFs = fs;
await memFs.file(testApp).writeAsString('main() {}');
await memFs.file(testFile).writeAsString('main() {}');
final List<String> args = <String>[
'drive',
'--target=$testApp',
'--no-pub',
'--sound-null-safety',
];
await createTestCommandRunner(command).run(args);
expect(testLogger.errorText, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
group('findTargetDevice', () {
testUsingContext('uses specified device', () async {
testDeviceManager.specifiedDeviceId = '123';
final Device mockDevice = MockDevice();
testDeviceManager.addDevice(mockDevice);
when(mockDevice.name).thenReturn('specified-device');
when(mockDevice.id).thenReturn('123');
final Device device = await findTargetDevice(timeout: null);
expect(device.name, 'specified-device');
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
});
void findTargetDeviceOnOperatingSystem(String operatingSystem) {
Platform platform() => FakePlatform(operatingSystem: operatingSystem);
testUsingContext('returns null if no devices found', () async {
expect(await findTargetDevice(timeout: null), isNull);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
Platform: platform,
});
testUsingContext('uses existing Android device', () async {
final Device mockDevice = MockAndroidDevice();
when(mockDevice.name).thenReturn('mock-android-device');
testDeviceManager.addDevice(mockDevice);
final Device device = await findTargetDevice(timeout: null);
expect(device.name, 'mock-android-device');
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
Platform: platform,
});
testUsingContext('skips unsupported device', () async {
final Device mockDevice = MockAndroidDevice();
mockUnsupportedDevice = MockDevice();
when(mockUnsupportedDevice.isSupportedForProject(any))
.thenReturn(false);
when(mockUnsupportedDevice.isSupported())
.thenReturn(false);
when(mockUnsupportedDevice.name).thenReturn('mock-web');
when(mockUnsupportedDevice.id).thenReturn('web-1');
when(mockUnsupportedDevice.targetPlatform).thenAnswer((_) => Future<TargetPlatform>(() => TargetPlatform.web_javascript));
when(mockUnsupportedDevice.isLocalEmulator).thenAnswer((_) => Future<bool>(() => false));
when(mockUnsupportedDevice.sdkNameAndVersion).thenAnswer((_) => Future<String>(() => 'html5'));
when(mockDevice.name).thenReturn('mock-android-device');
when(mockDevice.id).thenReturn('mad-28');
when(mockDevice.isSupported())
.thenReturn(true);
when(mockDevice.isSupportedForProject(any))
.thenReturn(true);
when(mockDevice.targetPlatform).thenAnswer((_) => Future<TargetPlatform>(() => TargetPlatform.android_x64));
when(mockDevice.isLocalEmulator).thenAnswer((_) => Future<bool>(() => false));
when(mockDevice.sdkNameAndVersion).thenAnswer((_) => Future<String>(() => 'sdk-28'));
testDeviceManager.addDevice(mockDevice);
testDeviceManager.addDevice(mockUnsupportedDevice);
final Device device = await findTargetDevice(timeout: null);
expect(device.name, 'mock-android-device');
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
Platform: platform,
});
}
group('findTargetDevice on Linux', () {
findTargetDeviceOnOperatingSystem('linux');
});
group('findTargetDevice on Windows', () {
findTargetDeviceOnOperatingSystem('windows');
});
group('findTargetDevice on macOS', () {
findTargetDeviceOnOperatingSystem('macos');
Platform macOsPlatform() => FakePlatform(operatingSystem: 'macos');
testUsingContext('uses existing simulator', () async {
final Device mockDevice = MockDevice();
testDeviceManager.addDevice(mockDevice);
when(mockDevice.name).thenReturn('mock-simulator');
when(mockDevice.isLocalEmulator)
.thenAnswer((Invocation invocation) => Future<bool>.value(true));
final Device device = await findTargetDevice(timeout: null);
expect(device.name, 'mock-simulator');
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
Platform: macOsPlatform,
});
});
group('build arguments', () {
String testApp, testFile;
setUp(() {
restoreAppStarter();
});
Future<Device> appStarterSetup() async {
final Device mockDevice = MockDevice();
applyDdsMocks(mockDevice);
testDeviceManager.addDevice(mockDevice);
final FakeDeviceLogReader mockDeviceLogReader = FakeDeviceLogReader();
when(mockDevice.getLogReader()).thenReturn(mockDeviceLogReader);
final MockLaunchResult mockLaunchResult = MockLaunchResult();
when(mockLaunchResult.started).thenReturn(true);
when(mockDevice.startApp(
null,
mainPath: anyNamed('mainPath'),
route: anyNamed('route'),
debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'),
prebuiltApplication: anyNamed('prebuiltApplication'),
userIdentifier: anyNamed('userIdentifier'),
)).thenAnswer((_) => Future<LaunchResult>.value(mockLaunchResult));
when(mockDevice.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier')))
.thenAnswer((_) => Future<bool>.value(false));
testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
testRunner = (List<String> testArgs, Map<String, String> environment) async {
throwToolExit(null, exitCode: 123);
};
appStopper = expectAsync2(
(DriveCommand command, ApplicationPackage package) async {
return true;
},
count: 2,
);
final MemoryFileSystem memFs = fs;
await memFs.file(testApp).writeAsString('main() {}');
await memFs.file(testFile).writeAsString('main() {}');
return mockDevice;
}
testUsingContext('does not use pre-built app if no build arg provided', () async {
final Device mockDevice = await appStarterSetup();
final List<String> args = <String>[
'drive',
'--target=$testApp',
'--no-pub',
];
try {
await createTestCommandRunner(command).run(args);
} on ToolExit catch (e) {
expect(e.exitCode, 123);
expect(e.message, null);
}
verify(mockDevice.startApp(
null,
mainPath: anyNamed('mainPath'),
route: anyNamed('route'),
debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'),
prebuiltApplication: false,
userIdentifier: anyNamed('userIdentifier'),
));
verify(mockDevice.dispose());
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('does not use pre-built app if --build arg provided', () async {
final Device mockDevice = await appStarterSetup();
final List<String> args = <String>[
'drive',
'--build',
'--target=$testApp',
'--no-pub',
];
try {
await createTestCommandRunner(command).run(args);
} on ToolExit catch (e) {
expect(e.exitCode, 123);
expect(e.message, null);
}
verify(mockDevice.startApp(
null,
mainPath: anyNamed('mainPath'),
route: anyNamed('route'),
debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'),
prebuiltApplication: false,
userIdentifier: anyNamed('userIdentifier'),
));
verify(mockDevice.dispose());
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
});
group('debugging options', () {
DebuggingOptions debuggingOptions;
String testApp, testFile;
setUp(() {
restoreAppStarter();
});
Future<Device> appStarterSetup() async {
final Device mockDevice = MockDevice();
applyDdsMocks(mockDevice);
testDeviceManager.addDevice(mockDevice);
final FakeDeviceLogReader mockDeviceLogReader = FakeDeviceLogReader();
when(mockDevice.getLogReader()).thenReturn(mockDeviceLogReader);
final MockLaunchResult mockLaunchResult = MockLaunchResult();
when(mockLaunchResult.started).thenReturn(true);
when(mockDevice.startApp(
null,
mainPath: anyNamed('mainPath'),
route: anyNamed('route'),
debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'),
prebuiltApplication: anyNamed('prebuiltApplication'),
userIdentifier: anyNamed('userIdentifier'),
)).thenAnswer((Invocation invocation) async {
debuggingOptions = invocation.namedArguments[#debuggingOptions] as DebuggingOptions;
return mockLaunchResult;
});
when(mockDevice.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier')))
.thenAnswer((_) => Future<bool>.value(false));
testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
testRunner = (List<String> testArgs, Map<String, String> environment) async {
throwToolExit(null, exitCode: 123);
};
appStopper = expectAsync2(
(DriveCommand command, ApplicationPackage package) async {
return true;
},
count: 2,
);
final MemoryFileSystem memFs = fs;
await memFs.file(testApp).writeAsString('main() {}');
await memFs.file(testFile).writeAsString('main() {}');
return mockDevice;
}
void _testOptionThatDefaultsToFalse(
String optionName,
bool setToTrue,
bool optionValue(),
) {
testUsingContext('$optionName ${setToTrue ? 'works' : 'defaults to false'}', () async {
final Device mockDevice = await appStarterSetup();
final List<String> args = <String>[
'drive',
'--target=$testApp',
if (setToTrue) optionName,
'--no-pub',
];
try {
await createTestCommandRunner(command).run(args);
} on ToolExit catch (e) {
expect(e.exitCode, 123);
expect(e.message, null);
}
verify(mockDevice.startApp(
null,
mainPath: anyNamed('mainPath'),
route: anyNamed('route'),
debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'),
prebuiltApplication: false,
userIdentifier: anyNamed('userIdentifier'),
));
expect(optionValue(), setToTrue ? isTrue : isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
}
void testOptionThatDefaultsToFalse(
String optionName,
bool optionValue(),
) {
_testOptionThatDefaultsToFalse(optionName, true, optionValue);
_testOptionThatDefaultsToFalse(optionName, false, optionValue);
}
testOptionThatDefaultsToFalse(
'--dump-skp-on-shader-compilation',
() => debuggingOptions.dumpSkpOnShaderCompilation,
);
testOptionThatDefaultsToFalse(
'--verbose-system-logs',
() => debuggingOptions.verboseSystemLogs,
);
testOptionThatDefaultsToFalse(
'--cache-sksl',
() => debuggingOptions.cacheSkSL,
);
testOptionThatDefaultsToFalse(
'--purge-persistent-cache',
() => debuggingOptions.purgePersistentCache,
);
testOptionThatDefaultsToFalse(
'--publish-port',
() => !debuggingOptions.disablePortPublication,
);
});
});
group('getDesiredCapabilities', () {
test('Chrome with headless on', () {
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ sync_io.LogType.performance: 'ALL'},
'chromeOptions': <String, dynamic>{
'w3c': false,
'args': <String>[
'--bwsi',
'--disable-background-timer-throttling',
'--disable-default-apps',
'--disable-extensions',
'--disable-popup-blocking',
'--disable-translate',
'--no-default-browser-check',
'--no-sandbox',
'--no-first-run',
'--headless'
],
'perfLoggingPrefs': <String, String>{
'traceCategories':
'devtools.timeline,'
'v8,blink.console,benchmark,blink,'
'blink.user_timing'
}
}
};
expect(getDesiredCapabilities(Browser.chrome, true), expected);
});
test('Chrome with headless off', () {
const String chromeBinary = 'random-binary';
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ sync_io.LogType.performance: 'ALL'},
'chromeOptions': <String, dynamic>{
'binary': chromeBinary,
'w3c': false,
'args': <String>[
'--bwsi',
'--disable-background-timer-throttling',
'--disable-default-apps',
'--disable-extensions',
'--disable-popup-blocking',
'--disable-translate',
'--no-default-browser-check',
'--no-sandbox',
'--no-first-run',
],
'perfLoggingPrefs': <String, String>{
'traceCategories':
'devtools.timeline,'
'v8,blink.console,benchmark,blink,'
'blink.user_timing'
}
}
};
expect(getDesiredCapabilities(Browser.chrome, false, chromeBinary), expected);
});
test('Firefox with headless on', () {
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'firefox',
'moz:firefoxOptions' : <String, dynamic>{
'args': <String>['-headless'],
'prefs': <String, dynamic>{
'dom.file.createInChild': true,
'dom.timeout.background_throttling_max_budget': -1,
'media.autoplay.default': 0,
'media.gmp-manager.url': '',
'media.gmp-provider.enabled': false,
'network.captive-portal-service.enabled': false,
'security.insecure_field_warning.contextual.enabled': false,
'test.currentTimeOffsetSeconds': 11491200
},
'log': <String, String>{'level': 'trace'}
}
};
expect(getDesiredCapabilities(Browser.firefox, true), expected);
});
test('Firefox with headless off', () {
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'firefox',
'moz:firefoxOptions' : <String, dynamic>{
'args': <String>[],
'prefs': <String, dynamic>{
'dom.file.createInChild': true,
'dom.timeout.background_throttling_max_budget': -1,
'media.autoplay.default': 0,
'media.gmp-manager.url': '',
'media.gmp-provider.enabled': false,
'network.captive-portal-service.enabled': false,
'security.insecure_field_warning.contextual.enabled': false,
'test.currentTimeOffsetSeconds': 11491200
},
'log': <String, String>{'level': 'trace'}
}
};
expect(getDesiredCapabilities(Browser.firefox, false), expected);
});
test('Edge', () {
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'edge',
};
expect(getDesiredCapabilities(Browser.edge, false), expected);
});
test('macOS Safari', () {
final Map<String, dynamic> expected = <String, dynamic>{
'browserName': 'safari',
};
expect(getDesiredCapabilities(Browser.safari, false), expected);
});
test('iOS Safari', () {
final Map<String, dynamic> expected = <String, dynamic>{
'platformName': 'ios',
'browserName': 'safari',
'safari:useSimulator': true
};
expect(getDesiredCapabilities(Browser.iosSafari, false), expected);
});
test('android chrome', () {
final Map<String, dynamic> expected = <String, dynamic>{
'browserName': 'chrome',
'platformName': 'android',
'goog:chromeOptions': <String, dynamic>{
'androidPackage': 'com.android.chrome',
'args': <String>['--disable-fullscreen']
},
};
expect(getDesiredCapabilities(Browser.androidChrome, false), expected);
});
});
testUsingContext('Can write SkSL file with provided output file', () async {
final MockDevice device = MockDevice();
when(device.name).thenReturn('foo');
when(device.targetPlatform).thenAnswer((Invocation invocation) async {
return TargetPlatform.android_arm;
});
final File outputFile = globals.fs.file('out/foo');
final String result = await sharedSkSlWriter(
device,
<String, Object>{'foo': 'bar'},
outputFile: outputFile,
);
expect(result, 'out/foo');
expect(outputFile, exists);
expect(outputFile.readAsStringSync(), '{"platform":"android","name":"foo","engineRevision":null,"data":{"foo":"bar"}}');
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
}
class MockDevice extends Mock implements Device {
MockDevice() {
when(isSupported()).thenReturn(true);
}
}
class MockAndroidDevice extends Mock implements AndroidDevice { }
class MockDartDevelopmentService extends Mock implements DartDevelopmentService { }
class MockLaunchResult extends Mock implements LaunchResult { }
class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory {
@override
Future<ApplicationPackage> getPackageForPlatform(TargetPlatform platform, {BuildInfo buildInfo, File applicationBinary}) {
return null;
}
}
...@@ -77,6 +77,7 @@ void main() { ...@@ -77,6 +77,7 @@ void main() {
..writeAsStringSync('void main() {}'); ..writeAsStringSync('void main() {}');
environment.defines[kTargetFile] = mainFile.path; environment.defines[kTargetFile] = mainFile.path;
environment.defines[kHasWebPlugins] = 'true'; environment.defines[kHasWebPlugins] = 'true';
environment.defines[kInitializePlatform] = 'true';
await const WebEntrypointTarget().build(environment); await const WebEntrypointTarget().build(environment);
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
...@@ -85,6 +86,9 @@ void main() { ...@@ -85,6 +86,9 @@ void main() {
expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';")); expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
expect(generated, contains('registerPlugins(webPluginRegistry);')); expect(generated, contains('registerPlugins(webPluginRegistry);'));
// Platform
expect(generated, contains('if (true) {'));
// Main // Main
expect(generated, contains('entrypoint.main();')); expect(generated, contains('entrypoint.main();'));
...@@ -179,6 +183,7 @@ void main() { ...@@ -179,6 +183,7 @@ void main() {
environment.defines[kTargetFile] = mainFile.path; environment.defines[kTargetFile] = mainFile.path;
environment.defines[kHasWebPlugins] = 'true'; environment.defines[kHasWebPlugins] = 'true';
environment.defines[kInitializePlatform] = 'true';
await const WebEntrypointTarget().build(environment); await const WebEntrypointTarget().build(environment);
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
...@@ -187,6 +192,9 @@ void main() { ...@@ -187,6 +192,9 @@ void main() {
expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';")); expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
expect(generated, contains('registerPlugins(webPluginRegistry);')); expect(generated, contains('registerPlugins(webPluginRegistry);'));
// Platform
expect(generated, contains('if (true) {'));
// Main // Main
expect(generated, contains('entrypoint.main();')); expect(generated, contains('entrypoint.main();'));
...@@ -202,6 +210,7 @@ void main() { ...@@ -202,6 +210,7 @@ void main() {
..writeAsStringSync('void main() {}'); ..writeAsStringSync('void main() {}');
environment.defines[kTargetFile] = mainFile.path; environment.defines[kTargetFile] = mainFile.path;
environment.defines[kHasWebPlugins] = 'false'; environment.defines[kHasWebPlugins] = 'false';
environment.defines[kInitializePlatform] = 'true';
await const WebEntrypointTarget().build(environment); await const WebEntrypointTarget().build(environment);
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
...@@ -209,6 +218,32 @@ void main() { ...@@ -209,6 +218,32 @@ void main() {
// Plugins // Plugins
expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';"))); expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';")));
expect(generated, isNot(contains('registerPlugins(webPluginRegistry);'))); expect(generated, isNot(contains('registerPlugins(webPluginRegistry);')));
// Platform
expect(generated, contains('if (true) {'));
// Main
expect(generated, contains('entrypoint.main();'));
}));
test('WebEntrypointTarget generates an entrypoint with plugins and without init platform', () => testbed.run(() async {
final File mainFile = globals.fs.file(globals.fs.path.join('foo', 'lib', 'main.dart'))
..createSync(recursive: true)
..writeAsStringSync('void main() {}');
environment.defines[kTargetFile] = mainFile.path;
environment.defines[kHasWebPlugins] = 'true';
environment.defines[kInitializePlatform] = 'false';
await const WebEntrypointTarget().build(environment);
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
// Plugins
expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
expect(generated, contains('registerPlugins(webPluginRegistry);'));
// Platform
expect(generated, contains('if (false) {'));
// Main // Main
expect(generated, contains('entrypoint.main();')); expect(generated, contains('entrypoint.main();'));
})); }));
...@@ -247,6 +282,7 @@ void main() { ...@@ -247,6 +282,7 @@ void main() {
..writeAsStringSync('void main() {}'); ..writeAsStringSync('void main() {}');
environment.defines[kTargetFile] = mainFile.path; environment.defines[kTargetFile] = mainFile.path;
environment.defines[kHasWebPlugins] = 'false'; environment.defines[kHasWebPlugins] = 'false';
environment.defines[kInitializePlatform] = 'false';
await const WebEntrypointTarget().build(environment); await const WebEntrypointTarget().build(environment);
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
...@@ -255,6 +291,9 @@ void main() { ...@@ -255,6 +291,9 @@ void main() {
expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';"))); expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';")));
expect(generated, isNot(contains('registerPlugins(webPluginRegistry);'))); expect(generated, isNot(contains('registerPlugins(webPluginRegistry);')));
// Platform
expect(generated, contains('if (false) {'));
// Main // Main
expect(generated, contains('entrypoint.main();')); expect(generated, contains('entrypoint.main();'));
})); }));
......
...@@ -55,7 +55,7 @@ void main() { ...@@ -55,7 +55,7 @@ void main() {
testWithoutContext('Install and uninstall are no-ops that report success', () async { testWithoutContext('Install and uninstall are no-ops that report success', () async {
final FakeDesktopDevice device = setUpDesktopDevice(); final FakeDesktopDevice device = setUpDesktopDevice();
final FakeApplicationPackage package = FakeApplicationPackage(); final FakeAppplicationPackage package = FakeAppplicationPackage();
expect(await device.uninstallApp(package), true); expect(await device.uninstallApp(package), true);
expect(await device.isAppInstalled(package), true); expect(await device.isAppInstalled(package), true);
...@@ -71,7 +71,7 @@ void main() { ...@@ -71,7 +71,7 @@ void main() {
group('Starting and stopping application', () { group('Starting and stopping application', () {
testWithoutContext('Stop without start is a successful no-op', () async { testWithoutContext('Stop without start is a successful no-op', () async {
final FakeDesktopDevice device = setUpDesktopDevice(); final FakeDesktopDevice device = setUpDesktopDevice();
final FakeApplicationPackage package = FakeApplicationPackage(); final FakeAppplicationPackage package = FakeAppplicationPackage();
expect(await device.stopApp(package), true); expect(await device.stopApp(package), true);
}); });
...@@ -89,7 +89,7 @@ void main() { ...@@ -89,7 +89,7 @@ void main() {
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager, fileSystem: fileSystem); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager, fileSystem: fileSystem);
final String executableName = device.executablePathForDevice(null, BuildMode.debug); final String executableName = device.executablePathForDevice(null, BuildMode.debug);
fileSystem.file(executableName).writeAsStringSync('\n'); fileSystem.file(executableName).writeAsStringSync('\n');
final FakeApplicationPackage package = FakeApplicationPackage(); final FakeAppplicationPackage package = FakeAppplicationPackage();
final LaunchResult result = await device.startApp( final LaunchResult result = await device.startApp(
package, package,
prebuiltApplication: true, prebuiltApplication: true,
...@@ -103,7 +103,7 @@ void main() { ...@@ -103,7 +103,7 @@ void main() {
testWithoutContext('Null executable path fails gracefully', () async { testWithoutContext('Null executable path fails gracefully', () async {
final BufferLogger logger = BufferLogger.test(); final BufferLogger logger = BufferLogger.test();
final DesktopDevice device = setUpDesktopDevice(nullExecutablePathForDevice: true, logger: logger); final DesktopDevice device = setUpDesktopDevice(nullExecutablePathForDevice: true, logger: logger);
final FakeApplicationPackage package = FakeApplicationPackage(); final FakeAppplicationPackage package = FakeAppplicationPackage();
final LaunchResult result = await device.startApp( final LaunchResult result = await device.startApp(
package, package,
prebuiltApplication: true, prebuiltApplication: true,
...@@ -124,7 +124,7 @@ void main() { ...@@ -124,7 +124,7 @@ void main() {
), ),
]); ]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
final FakeApplicationPackage package = FakeApplicationPackage(); final FakeAppplicationPackage package = FakeAppplicationPackage();
final LaunchResult result = await device.startApp( final LaunchResult result = await device.startApp(
package, package,
prebuiltApplication: true, prebuiltApplication: true,
...@@ -168,7 +168,7 @@ void main() { ...@@ -168,7 +168,7 @@ void main() {
), ),
]); ]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
final FakeApplicationPackage package = FakeApplicationPackage(); final FakeAppplicationPackage package = FakeAppplicationPackage();
final LaunchResult result = await device.startApp( final LaunchResult result = await device.startApp(
package, package,
prebuiltApplication: true, prebuiltApplication: true,
...@@ -191,6 +191,7 @@ void main() { ...@@ -191,6 +191,7 @@ void main() {
purgePersistentCache: true, purgePersistentCache: true,
useTestFonts: true, useTestFonts: true,
verboseSystemLogs: true, verboseSystemLogs: true,
initializePlatform: true,
nullAssertions: true, nullAssertions: true,
), ),
); );
...@@ -216,7 +217,7 @@ void main() { ...@@ -216,7 +217,7 @@ void main() {
), ),
]); ]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
final FakeApplicationPackage package = FakeApplicationPackage(); final FakeAppplicationPackage package = FakeAppplicationPackage();
final LaunchResult result = await device.startApp( final LaunchResult result = await device.startApp(
package, package,
prebuiltApplication: true, prebuiltApplication: true,
...@@ -227,6 +228,7 @@ void main() { ...@@ -227,6 +228,7 @@ void main() {
BuildInfo.debug, BuildInfo.debug,
traceAllowlist: 'foo,bar', traceAllowlist: 'foo,bar',
cacheSkSL: true, cacheSkSL: true,
initializePlatform: true,
), ),
); );
...@@ -323,7 +325,7 @@ class FakeDesktopDevice extends DesktopDevice { ...@@ -323,7 +325,7 @@ class FakeDesktopDevice extends DesktopDevice {
} }
} }
class FakeApplicationPackage extends Fake implements ApplicationPackage {} class FakeAppplicationPackage extends Fake implements ApplicationPackage {}
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils { class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
@override @override
String get name => 'Example'; String get name => 'Example';
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/dds.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/drive/drive_service.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import '../../src/common.dart';
import '../../src/context.dart';
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
id: '1',
pauseEvent: vm_service.Event(
kind: vm_service.EventKind.kResume,
timestamp: 0
),
breakpoints: <vm_service.Breakpoint>[],
exceptionPauseMode: null,
libraries: <vm_service.LibraryRef>[
vm_service.LibraryRef(
id: '1',
uri: 'file:///hello_world/main.dart',
name: '',
),
],
livePorts: 0,
name: 'test',
number: '1',
pauseOnExit: false,
runnable: true,
startTime: 0,
);
final vm_service.Isolate fakePausedIsolate = vm_service.Isolate(
id: '1',
pauseEvent: vm_service.Event(
kind: vm_service.EventKind.kPauseException,
timestamp: 0
),
breakpoints: <vm_service.Breakpoint>[
vm_service.Breakpoint(
breakpointNumber: 123,
id: 'test-breakpoint',
location: vm_service.SourceLocation(
tokenPos: 0,
script: vm_service.ScriptRef(id: 'test-script', uri: 'foo.dart'),
),
resolved: true,
),
],
exceptionPauseMode: null,
libraries: <vm_service.LibraryRef>[],
livePorts: 0,
name: 'test',
number: '1',
pauseOnExit: false,
runnable: true,
startTime: 0,
);
final vm_service.VM fakeVM = vm_service.VM(
isolates: <vm_service.IsolateRef>[fakeUnpausedIsolate],
pid: 1,
hostCPU: '',
isolateGroups: <vm_service.IsolateGroupRef>[],
targetCPU: '',
startTime: 0,
name: 'dart',
architectureBits: 64,
operatingSystem: '',
version: '',
);
final FlutterView fakeFlutterView = FlutterView(
id: 'a',
uiIsolate: fakeUnpausedIsolate,
);
final FakeVmServiceRequest listViews = FakeVmServiceRequest(
method: kListViewsMethod,
jsonResponse: <String, Object>{
'views': <Object>[
fakeFlutterView.toJson(),
],
},
);
final FakeVmServiceRequest getVM = FakeVmServiceRequest(
method: 'getVM',
args: <String, Object>{},
jsonResponse: fakeVM.toJson(),
);
void main() {
testWithoutContext('Exits if device fails to start', () {
final DriverService driverService = setUpDriverService();
final Device device = FakeDevice(LaunchResult.failed());
expect(
() => driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true),
throwsToolExit(message: 'Application failed to start. Will not run test. Quitting.'),
);
});
testWithoutContext('Retries application launch if it fails the first time', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', '--enable-experiment=non-nullable', 'foo.test', '-rexpanded'],
exitCode: 23,
environment: <String, String>{
'FOO': 'BAR',
'VM_SERVICE_URL': 'http://127.0.0.1:1234/' // dds forwarded URI
},
),
]);
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final Device device = FakeDevice(LaunchResult.succeeded(
observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
))..failOnce = true;
await expectLater(
() async => await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true),
returnsNormally,
);
});
testWithoutContext('Connects to device VM Service and runs test application', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', '--enable-experiment=non-nullable', 'foo.test', '-rexpanded'],
exitCode: 23,
environment: <String, String>{
'FOO': 'BAR',
'VM_SERVICE_URL': 'http://127.0.0.1:1234/' // dds forwarded URI
},
),
]);
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final Device device = FakeDevice(LaunchResult.succeeded(
observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true);
final int testResult = await driverService.startTest(
'foo.test',
<String>['--enable-experiment=non-nullable'],
<String, String>{'FOO': 'BAR'},
);
expect(testResult, 23);
});
testWithoutContext('Connects to device VM Service and runs test application without dds', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', 'foo.test', '-rexpanded'],
exitCode: 11,
environment: <String, String>{
'VM_SERVICE_URL': 'http://127.0.0.1:1234/'
},
),
]);
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final Device device = FakeDevice(LaunchResult.succeeded(
observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile, disableDds: true), true);
final int testResult = await driverService.startTest(
'foo.test',
<String>[],
<String, String>{},
);
expect(testResult, 11);
});
testWithoutContext('Safely stops and uninstalls application', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[]);
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final FakeDevice device = FakeDevice(LaunchResult.succeeded(
observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true);
await driverService.stop();
expect(device.didStopApp, true);
expect(device.didUninstallApp, true);
expect(device.didDispose, true);
});
// FlutterVersion requires context.
testUsingContext('Writes SkSL to file when provided with out file', () async {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
listViews,
const FakeVmServiceRequest(
method: '_flutter.getSkSLs',
args: <String, Object>{
'viewId': 'a'
},
jsonResponse: <String, Object>{
'SkSLs': <String, Object>{
'A': 'B',
}
}
),
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[]);
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final FakeDevice device = FakeDevice(LaunchResult.succeeded(
observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true);
await driverService.stop(writeSkslOnExit: fileSystem.file('out.json'));
expect(device.didStopApp, true);
expect(device.didUninstallApp, true);
expect(json.decode(fileSystem.file('out.json').readAsStringSync()), <String, Object>{
'platform': 'android',
'name': 'test',
'engineRevision': null,
'data': <String, Object>{'A': 'B'}
});
});
}
FlutterDriverService setUpDriverService({
Logger logger,
ProcessManager processManager,
vm_service.VmService vmService,
}) {
logger ??= BufferLogger.test();
return FlutterDriverService(
applicationPackageFactory: FakeApplicationPackageFactory(FakeApplicationPackage()),
logger: logger,
processUtils: ProcessUtils(
logger: logger,
processManager: processManager ?? FakeProcessManager.any(),
),
dartSdkPath: 'dart',
vmServiceConnector: (Uri httpUri, {
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
Object compression,
Device device,
}) async {
return vmService;
}
);
}
class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory {
FakeApplicationPackageFactory(this.applicationPackage);
ApplicationPackage applicationPackage;
@override
Future<ApplicationPackage> getPackageForPlatform(
TargetPlatform platform, {
BuildInfo buildInfo,
File applicationBinary,
}) async => applicationPackage;
}
class FakeApplicationPackage extends Fake implements ApplicationPackage {}
class FakeDevice extends Fake implements Device {
FakeDevice(this.result);
LaunchResult result;
bool didStopApp = false;
bool didUninstallApp = false;
bool didDispose = false;
bool failOnce = false;
@override
String get name => 'test';
@override
final DartDevelopmentService dds = FakeDartDevelopmentService();
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm;
@override
Future<DeviceLogReader> getLogReader({
covariant ApplicationPackage app,
bool includePastLogs = false,
}) async => NoOpDeviceLogReader('test');
@override
Future<LaunchResult> startApp(
covariant ApplicationPackage package, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool ipv6 = false,
String userIdentifier,
}) async {
if (failOnce) {
failOnce = false;
return LaunchResult.failed();
}
return result;
}
@override
Future<bool> stopApp(covariant ApplicationPackage app, {String userIdentifier}) async {
didStopApp = true;
return true;
}
@override
Future<bool> uninstallApp(covariant ApplicationPackage app, {String userIdentifier}) async {
didUninstallApp = true;
return true;
}
@override
Future<void> dispose() async {
didDispose = true;
}
}
class FakeDartDevelopmentService extends Fake implements DartDevelopmentService {
bool started = false;
bool disposed = false;
@override
final Uri uri = Uri.parse('http://127.0.0.1:1234/');
@override
Future<void> startDartDevelopmentService(
Uri observatoryUri,
int hostPort,
bool ipv6,
bool disableServiceAuthCodes,
) async {
started = true;
}
@override
Future<void> shutdown() async {
disposed = true;
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/drive/web_driver_service.dart';
import 'package:webdriver/sync_io.dart' as sync_io;
import '../../src/common.dart';
void main() {
testWithoutContext('getDesiredCapabilities Chrome with headless on', () {
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ sync_io.LogType.performance: 'ALL'},
'chromeOptions': <String, dynamic>{
'w3c': false,
'args': <String>[
'--bwsi',
'--disable-background-timer-throttling',
'--disable-default-apps',
'--disable-extensions',
'--disable-popup-blocking',
'--disable-translate',
'--no-default-browser-check',
'--no-sandbox',
'--no-first-run',
'--headless'
],
'perfLoggingPrefs': <String, String>{
'traceCategories':
'devtools.timeline,'
'v8,blink.console,benchmark,blink,'
'blink.user_timing'
}
}
};
expect(getDesiredCapabilities(Browser.chrome, true), expected);
});
testWithoutContext('getDesiredCapabilities Chrome with headless off', () {
const String chromeBinary = 'random-binary';
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ sync_io.LogType.performance: 'ALL'},
'chromeOptions': <String, dynamic>{
'binary': chromeBinary,
'w3c': false,
'args': <String>[
'--bwsi',
'--disable-background-timer-throttling',
'--disable-default-apps',
'--disable-extensions',
'--disable-popup-blocking',
'--disable-translate',
'--no-default-browser-check',
'--no-sandbox',
'--no-first-run',
],
'perfLoggingPrefs': <String, String>{
'traceCategories':
'devtools.timeline,'
'v8,blink.console,benchmark,blink,'
'blink.user_timing'
}
}
};
expect(getDesiredCapabilities(Browser.chrome, false, chromeBinary), expected);
});
testWithoutContext('getDesiredCapabilities Firefox with headless on', () {
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'firefox',
'moz:firefoxOptions' : <String, dynamic>{
'args': <String>['-headless'],
'prefs': <String, dynamic>{
'dom.file.createInChild': true,
'dom.timeout.background_throttling_max_budget': -1,
'media.autoplay.default': 0,
'media.gmp-manager.url': '',
'media.gmp-provider.enabled': false,
'network.captive-portal-service.enabled': false,
'security.insecure_field_warning.contextual.enabled': false,
'test.currentTimeOffsetSeconds': 11491200
},
'log': <String, String>{'level': 'trace'}
}
};
expect(getDesiredCapabilities(Browser.firefox, true), expected);
});
testWithoutContext('getDesiredCapabilities Firefox with headless off', () {
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'firefox',
'moz:firefoxOptions' : <String, dynamic>{
'args': <String>[],
'prefs': <String, dynamic>{
'dom.file.createInChild': true,
'dom.timeout.background_throttling_max_budget': -1,
'media.autoplay.default': 0,
'media.gmp-manager.url': '',
'media.gmp-provider.enabled': false,
'network.captive-portal-service.enabled': false,
'security.insecure_field_warning.contextual.enabled': false,
'test.currentTimeOffsetSeconds': 11491200
},
'log': <String, String>{'level': 'trace'}
}
};
expect(getDesiredCapabilities(Browser.firefox, false), expected);
});
testWithoutContext('getDesiredCapabilities Edge', () {
final Map<String, dynamic> expected = <String, dynamic>{
'acceptInsecureCerts': true,
'browserName': 'edge',
};
expect(getDesiredCapabilities(Browser.edge, false), expected);
});
testWithoutContext('getDesiredCapabilities macOS Safari', () {
final Map<String, dynamic> expected = <String, dynamic>{
'browserName': 'safari',
};
expect(getDesiredCapabilities(Browser.safari, false), expected);
});
testWithoutContext('getDesiredCapabilities iOS Safari', () {
final Map<String, dynamic> expected = <String, dynamic>{
'platformName': 'ios',
'browserName': 'safari',
'safari:useSimulator': true
};
expect(getDesiredCapabilities(Browser.iosSafari, false), expected);
});
testWithoutContext('getDesiredCapabilities android chrome', () {
final Map<String, dynamic> expected = <String, dynamic>{
'browserName': 'chrome',
'platformName': 'android',
'goog:chromeOptions': <String, dynamic>{
'androidPackage': 'com.android.chrome',
'args': <String>['--disable-fullscreen']
},
};
expect(getDesiredCapabilities(Browser.androidChrome, false), expected);
});
}
...@@ -24,6 +24,7 @@ import 'package:flutter_tools/src/convert.dart'; ...@@ -24,6 +24,7 @@ import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_cold.dart'; import 'package:flutter_tools/src/run_cold.dart';
...@@ -2385,6 +2386,7 @@ void main() { ...@@ -2385,6 +2386,7 @@ void main() {
treeShakeIcons: false, treeShakeIcons: false,
nullSafetyMode: NullSafetyMode.unsound, nullSafetyMode: NullSafetyMode.unsound,
), ),
flutterProject: FlutterProject.current(),
target: null, target: null,
platform: FakePlatform(operatingSystem: 'linux'), platform: FakePlatform(operatingSystem: 'linux'),
)).generator as DefaultResidentCompiler; )).generator as DefaultResidentCompiler;
...@@ -2419,6 +2421,7 @@ void main() { ...@@ -2419,6 +2421,7 @@ void main() {
treeShakeIcons: false, treeShakeIcons: false,
extraFrontEndOptions: <String>['--enable-experiment=non-nullable'], extraFrontEndOptions: <String>['--enable-experiment=non-nullable'],
), ),
flutterProject: FlutterProject.current(),
target: null, target: null,
platform: FakePlatform(operatingSystem: 'linux'), platform: FakePlatform(operatingSystem: 'linux'),
)).generator as DefaultResidentCompiler; )).generator as DefaultResidentCompiler;
...@@ -2453,6 +2456,7 @@ void main() { ...@@ -2453,6 +2456,7 @@ void main() {
treeShakeIcons: false, treeShakeIcons: false,
extraFrontEndOptions: <String>[], extraFrontEndOptions: <String>[],
), ),
flutterProject: FlutterProject.current(),
target: null, platform: null, target: null, platform: null,
)).generator as DefaultResidentCompiler; )).generator as DefaultResidentCompiler;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:file_testing/file_testing.dart'; import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import '../src/common.dart'; import '../src/common.dart';
import 'test_utils.dart'; import 'test_utils.dart';
...@@ -60,7 +61,7 @@ void main() { ...@@ -60,7 +61,7 @@ void main() {
final String outputFilePath = line.split(iosDebugMessage).last.trim(); final String outputFilePath = line.split(iosDebugMessage).last.trim();
expect(fileSystem.file(fileSystem.path.join(woringDirectory, outputFilePath)), exists); expect(fileSystem.file(fileSystem.path.join(woringDirectory, outputFilePath)), exists);
expect(result.exitCode, 0); expect(result.exitCode, 0);
}, skip: true); // Extremely flaky due to https://github.com/flutter/flutter/issues/68144 }, skip: !const LocalPlatform().isMacOS); // Only supported on macOS
testWithoutContext('--analyze-size is only supported in release mode', () async { testWithoutContext('--analyze-size is only supported in release mode', () async {
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment