Unverified Commit 183fe75d authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] reland: drive service (#68887)

Overhaul of flutter drive in order to deliver a better experience, namely:

* flutter run and flutter drive now share more flags, so code paths that were previously only testable on run are now testable on drive.
* Removes web-initialize-platform as this is no longer used
* flutter drive correctly sets up a logger that shows native exceptions, by connecting to the vm service.
* VM service connection now provides access to memory info without launching devtools (only for debug/profile mode)

Web changes

* Passes on the one test in the repo, otherwise the webdriver code has been isolated as much as possible

Additional NNBD related bug fixes:

No longer passes --enable-experiment to the test script. (FYI @blasten ). earlier we might have assumed that the flutter gallery benchmarks would be migrated along side the app and flutter driver, but only the app under test needs to be migrated. The test scripts should never be run with the experiment.
parent cf64f2e3
...@@ -95,7 +95,10 @@ Future<void> main(List<String> args) async { ...@@ -95,7 +95,10 @@ 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,13 +590,17 @@ class AndroidDevice extends Device { ...@@ -590,13 +590,17 @@ 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(
await getLogReader(), // Avoid using getLogReader, which returns a singleton instance, because the
// 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,
...@@ -669,8 +673,6 @@ class AndroidDevice extends Device { ...@@ -669,8 +673,6 @@ 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,9 +22,6 @@ import 'assets.dart'; ...@@ -22,9 +22,6 @@ 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';
...@@ -89,7 +86,6 @@ class WebEntrypointTarget extends Target { ...@@ -89,7 +86,6 @@ 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.
...@@ -137,9 +133,7 @@ import '$mainImport' as entrypoint; ...@@ -137,9 +133,7 @@ import '$mainImport' as entrypoint;
Future<void> main() async { Future<void> main() async {
registerPlugins(webPluginRegistry); registerPlugins(webPluginRegistry);
if ($shouldInitializePlatform) {
await ui.webOnlyInitializePlatform(); await ui.webOnlyInitializePlatform();
}
entrypoint.main(); entrypoint.main();
} }
'''; ''';
...@@ -152,9 +146,7 @@ import 'dart:ui' as ui; ...@@ -152,9 +146,7 @@ import 'dart:ui' as ui;
import '$mainImport' as entrypoint; import '$mainImport' as entrypoint;
Future<void> main() async { Future<void> main() async {
if ($shouldInitializePlatform) {
await ui.webOnlyInitializePlatform(); await ui.webOnlyInitializePlatform();
}
entrypoint.main(); entrypoint.main();
} }
'''; ''';
......
...@@ -384,7 +384,6 @@ known, it can be explicitly provided to attach via the command-line, e.g. ...@@ -384,7 +384,6 @@ 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,12 +25,6 @@ class BuildWebCommand extends BuildSubCommand { ...@@ -25,12 +25,6 @@ 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,
...@@ -92,7 +86,6 @@ class BuildWebCommand extends BuildSubCommand { ...@@ -92,7 +86,6 @@ 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,7 +466,6 @@ class AppDomain extends Domain { ...@@ -466,7 +466,6 @@ 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,29 +3,21 @@ ...@@ -3,29 +3,21 @@
// 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 '../vmservice.dart'; import '../web/web_device.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.
...@@ -51,7 +43,12 @@ import 'run.dart'; ...@@ -51,7 +43,12 @@ 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);
...@@ -109,7 +106,7 @@ class DriveCommand extends RunCommandBase { ...@@ -109,7 +106,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',
...@@ -127,10 +124,15 @@ class DriveCommand extends RunCommandBase { ...@@ -127,10 +124,15 @@ 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';
...@@ -140,20 +142,15 @@ class DriveCommand extends RunCommandBase { ...@@ -140,20 +142,15 @@ 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);
/// Subscription to log messages printed on the device or simulator. @override
// ignore: cancel_subscriptions bool get startPausedDefault => true;
StreamSubscription<String> _deviceLogSubscription;
@override @override
Future<void> validateCommand() async { Future<void> validateCommand() async {
if (userIdentifier != null) { if (userIdentifier != null) {
final Device device = await findTargetDevice(timeout: deviceDiscoveryTimeout); final Device device = await findTargetDevice();
if (device is! AndroidDevice) { if (device is! AndroidDevice) {
throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android'); throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
} }
...@@ -167,229 +164,70 @@ class DriveCommand extends RunCommandBase { ...@@ -167,229 +164,70 @@ class DriveCommand extends RunCommandBase {
if (testFile == null) { if (testFile == null) {
throwToolExit(null); throwToolExit(null);
} }
if (await _fileSystem.type(testFile) != FileSystemEntityType.file) {
_device = await findTargetDevice(timeout: deviceDiscoveryTimeout); throwToolExit('Test file not found: $testFile');
}
final Device device = await findTargetDevice(includeUnsupportedDevices: stringArg('use-application-binary') == null);
if (device == null) { if (device == null) {
throwToolExit(null); throwToolExit(null);
} }
if (await globals.fs.type(testFile) != FileSystemEntityType.file) { final bool web = device is WebServerDevice || device is ChromiumDevice;
throwToolExit('Test file not found: $testFile'); _flutterDriverFactory ??= FlutterDriverFactory(
} applicationPackageFactory: ApplicationPackageFactory.instance,
logger: _logger,
String observatoryUri; processUtils: globals.processUtils,
ResidentRunner residentRunner; dartSdkPath: globals.artifacts.getArtifactPath(Artifact.engineDartBinary),
);
final DriverService driverService = _flutterDriverFactory.createDriverService(web);
final BuildInfo buildInfo = getBuildInfo(); final BuildInfo buildInfo = getBuildInfo();
final bool isWebPlatform = await device.targetPlatform == TargetPlatform.web_javascript; final DebuggingOptions debuggingOptions = createDebuggingOptions();
final File applicationBinary = stringArg('use-application-binary') == null final File applicationBinary = stringArg('use-application-binary') == null
? null ? null
: globals.fs.file(stringArg('use-application-binary')); : _fileSystem.file(stringArg('use-application-binary'));
final ApplicationPackage package = await applicationPackages.getPackageForPlatform(
await device.targetPlatform,
buildInfo: buildInfo,
applicationBinary: applicationBinary,
);
if (argResults['use-existing-app'] == null) {
globals.printStatus('Starting application: $targetFile');
if (buildInfo.isRelease && !isWebPlatform) { await driverService.start(
// This is because we need VM service to be able to drive the app. buildInfo,
// For Flutter Web, testing in release mode is allowed.
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).'
);
}
Uri webUri;
if (isWebPlatform) {
// Start Flutter web application for current test
final FlutterProject flutterProject = FlutterProject.current();
final FlutterDevice flutterDevice = await FlutterDevice.create(
device, device,
flutterProject: flutterProject, debuggingOptions,
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;
}
// Attempt to launch the application up to 3 times, to validate whether it
// 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, ipv6,
disableServiceAuthCodes, applicationBinary: applicationBinary,
); route: route,
observatoryUri = device.dds.uri.toString(); userIdentifier: userIdentifier,
} on dds.DartDevelopmentServiceException catch(_) { mainPath: targetFile,
globals.printTrace('Note: DDS is already connected to $observatoryUri.'); platformArgs: <String, Object>{
} if (traceStartup)
} 'trace-startup': traceStartup,
} else { if (web)
globals.printStatus('Will connect to already running application instance.'); '--no-launch-chrome': true,
observatoryUri = stringArg('use-existing-app');
} }
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 { final int testResult = await driverService.startTest(
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, testFile,
], stringsArg('test-arguments'),
environment, <String, String>{},
); chromeBinary: stringArg('chrome-binary'),
} on Exception catch (error, stackTrace) { headless: boolArg('headless'),
if (error is ToolExit) { browserDimension: stringArg('browser-dimension').split(','),
rethrow; browserName: stringArg('browser-name'),
} driverPort: stringArg('driver-port') != null
throw Exception('Unable to run test: $error\n$stackTrace'); ? int.tryParse(stringArg('driver-port'))
} finally { : null,
await residentRunner?.exit(); androidEmulator: boolArg('android-emulator'),
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)) { if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) {
globals.printStatus('Leaving the application running.'); _logger.printStatus('Leaving the application running.');
} else { } else {
globals.printStatus('Stopping application instance.'); final File skslFile = stringArg('write-sksl-on-exit') != null
await appStopper(this, package); ? _fileSystem.file(stringArg('write-sksl-on-exit'))
: null;
await driverService.stop(userIdentifier: userIdentifier, writeSkslOnExit: skslFile);
} }
if (testResult != 0) {
await device?.dispose(); return FlutterCommandResult.fail();
} }
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
...@@ -400,28 +238,28 @@ $ex ...@@ -400,28 +238,28 @@ $ex
// 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 = globals.fs.path.normalize(targetFile); String appFile = _fileSystem.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 = globals.fs.currentDirectory.path; final String packageDir = _fileSystem.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 (!globals.fs.path.isRelative(appFile)) { if (!_fileSystem.path.isRelative(appFile)) {
if (!globals.fs.path.isWithin(packageDir, appFile)) { if (!_fileSystem.path.isWithin(packageDir, appFile)) {
globals.printError( _logger.printError(
'Application file $appFile is outside the package directory $packageDir' 'Application file $appFile is outside the package directory $packageDir'
); );
return null; return null;
} }
appFile = globals.fs.path.relative(appFile, from: packageDir); appFile = _fileSystem.path.relative(appFile, from: packageDir);
} }
final List<String> parts = globals.fs.path.split(appFile); final List<String> parts = _fileSystem.path.split(appFile);
if (parts.length < 2) { if (parts.length < 2) {
globals.printError( _logger.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.'
); );
...@@ -431,277 +269,8 @@ $ex ...@@ -431,277 +269,8 @@ $ex
// 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 = globals.fs.path.withoutExtension(globals.fs.path.joinAll( final String pathWithNoExtension = _fileSystem.path.withoutExtension(_fileSystem.path.joinAll(
<String>[packageDir, 'test_driver', ...parts.skip(1)])); <String>[packageDir, 'test_driver', ...parts.skip(1)]));
return '${pathWithNoExtension}_test${globals.fs.path.extension(appFile)}'; return '${pathWithNoExtension}_test${_fileSystem.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,8 +4,6 @@ ...@@ -4,8 +4,6 @@
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';
...@@ -25,8 +23,8 @@ import '../tracing.dart'; ...@@ -25,8 +23,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();
...@@ -77,6 +75,45 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -77,6 +75,45 @@ 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();
...@@ -96,7 +133,71 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -96,7 +133,71 @@ 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 {
...@@ -111,10 +212,6 @@ class RunCommand extends RunCommandBase { ...@@ -111,10 +212,6 @@ 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. '
...@@ -127,35 +224,6 @@ class RunCommand extends RunCommandBase { ...@@ -127,35 +224,6 @@ 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"), '
...@@ -176,16 +244,6 @@ class RunCommand extends RunCommandBase { ...@@ -176,16 +244,6 @@ 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.',
...@@ -248,6 +306,9 @@ class RunCommand extends RunCommandBase { ...@@ -248,6 +306,9 @@ 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;
...@@ -342,9 +403,6 @@ class RunCommand extends RunCommandBase { ...@@ -342,9 +403,6 @@ 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');
...@@ -372,73 +430,6 @@ class RunCommand extends RunCommandBase { ...@@ -372,73 +430,6 @@ 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
...@@ -464,11 +455,11 @@ class RunCommand extends RunCommandBase { ...@@ -464,11 +455,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: boolArg('track-widget-creation'), trackWidgetCreation: trackWidgetCreation,
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'),
...@@ -491,11 +482,6 @@ class RunCommand extends RunCommandBase { ...@@ -491,11 +482,6 @@ 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)) {
...@@ -534,7 +520,6 @@ class RunCommand extends RunCommandBase { ...@@ -534,7 +520,6 @@ 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,
...@@ -556,7 +541,7 @@ class RunCommand extends RunCommandBase { ...@@ -556,7 +541,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
...@@ -572,7 +557,7 @@ class RunCommand extends RunCommandBase { ...@@ -572,7 +557,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,
); );
...@@ -580,7 +565,7 @@ class RunCommand extends RunCommandBase { ...@@ -580,7 +565,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,7 +847,6 @@ class DebuggingOptions { ...@@ -847,7 +847,6 @@ 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,
...@@ -862,7 +861,6 @@ class DebuggingOptions { ...@@ -862,7 +861,6 @@ 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,
...@@ -913,8 +911,6 @@ class DebuggingOptions { ...@@ -913,8 +911,6 @@ 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: buildInfo.isRelease ?
DebuggingOptions.disabled(
buildInfo,
port: debuggingOptions.port,
)
: DebuggingOptions.enabled(
buildInfo,
port: debuggingOptions.port,
disablePortPublication: debuggingOptions.disablePortPublication,
),
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,7 +528,6 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -528,7 +528,6 @@ class _ResidentWebRunner extends ResidentWebRunner {
flutterProject, flutterProject,
target, target,
debuggingOptions.buildInfo, debuggingOptions.buildInfo,
debuggingOptions.initializePlatform,
false, false,
kNoneWorker, kNoneWorker,
true, true,
...@@ -596,7 +595,6 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -596,7 +595,6 @@ class _ResidentWebRunner extends ResidentWebRunner {
flutterProject, flutterProject,
target, target,
debuggingOptions.buildInfo, debuggingOptions.buildInfo,
debuggingOptions.initializePlatform,
false, false,
kNoneWorker, kNoneWorker,
true, true,
......
...@@ -70,7 +70,6 @@ class FlutterDevice { ...@@ -70,7 +70,6 @@ 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,6 +8,7 @@ import 'package:vm_service/vm_service.dart' as vm_service; ...@@ -8,6 +8,7 @@ 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';
...@@ -806,9 +807,11 @@ bool isPauseEvent(String kind) { ...@@ -806,9 +807,11 @@ 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) {
globals.logger.printStatus( 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'
...@@ -844,7 +847,7 @@ Future<String> sharedSkSlWriter(Device device, Map<String, Object> data, { ...@@ -844,7 +847,7 @@ Future<String> sharedSkSlWriter(Device device, Map<String, Object> data, {
'data': data, 'data': data,
}; };
outputFile.writeAsStringSync(json.encode(manifest)); outputFile.writeAsStringSync(json.encode(manifest));
globals.logger.printStatus('Wrote SkSL data to ${outputFile.path}.'); logger.printStatus('Wrote SkSL data to ${outputFile.path}.');
return outputFile.path; return outputFile.path;
} }
......
...@@ -26,7 +26,6 @@ Future<void> buildWeb( ...@@ -26,7 +26,6 @@ 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,
...@@ -54,7 +53,6 @@ Future<void> buildWeb( ...@@ -54,7 +53,6 @@ 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,7 +54,6 @@ void main() { ...@@ -54,7 +54,6 @@ 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,7 +77,6 @@ void main() { ...@@ -77,7 +77,6 @@ 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();
...@@ -86,9 +85,6 @@ void main() { ...@@ -86,9 +85,6 @@ 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();'));
...@@ -183,7 +179,6 @@ void main() { ...@@ -183,7 +179,6 @@ 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();
...@@ -192,9 +187,6 @@ void main() { ...@@ -192,9 +187,6 @@ 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();'));
...@@ -210,7 +202,6 @@ void main() { ...@@ -210,7 +202,6 @@ 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();
...@@ -218,32 +209,6 @@ void main() { ...@@ -218,32 +209,6 @@ 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();'));
})); }));
...@@ -282,7 +247,6 @@ void main() { ...@@ -282,7 +247,6 @@ 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();
...@@ -291,9 +255,6 @@ void main() { ...@@ -291,9 +255,6 @@ 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 FakeAppplicationPackage package = FakeAppplicationPackage(); final FakeApplicationPackage package = FakeApplicationPackage();
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 FakeAppplicationPackage package = FakeAppplicationPackage(); final FakeApplicationPackage package = FakeApplicationPackage();
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 FakeAppplicationPackage package = FakeAppplicationPackage(); final FakeApplicationPackage package = FakeApplicationPackage();
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 FakeAppplicationPackage package = FakeAppplicationPackage(); final FakeApplicationPackage package = FakeApplicationPackage();
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 FakeAppplicationPackage package = FakeAppplicationPackage(); final FakeApplicationPackage package = FakeApplicationPackage();
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 FakeAppplicationPackage package = FakeAppplicationPackage(); final FakeApplicationPackage package = FakeApplicationPackage();
final LaunchResult result = await device.startApp( final LaunchResult result = await device.startApp(
package, package,
prebuiltApplication: true, prebuiltApplication: true,
...@@ -191,7 +191,6 @@ void main() { ...@@ -191,7 +191,6 @@ void main() {
purgePersistentCache: true, purgePersistentCache: true,
useTestFonts: true, useTestFonts: true,
verboseSystemLogs: true, verboseSystemLogs: true,
initializePlatform: true,
nullAssertions: true, nullAssertions: true,
), ),
); );
...@@ -217,7 +216,7 @@ void main() { ...@@ -217,7 +216,7 @@ void main() {
), ),
]); ]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
final FakeAppplicationPackage package = FakeAppplicationPackage(); final FakeApplicationPackage package = FakeApplicationPackage();
final LaunchResult result = await device.startApp( final LaunchResult result = await device.startApp(
package, package,
prebuiltApplication: true, prebuiltApplication: true,
...@@ -228,7 +227,6 @@ void main() { ...@@ -228,7 +227,6 @@ void main() {
BuildInfo.debug, BuildInfo.debug,
traceAllowlist: 'foo,bar', traceAllowlist: 'foo,bar',
cacheSkSL: true, cacheSkSL: true,
initializePlatform: true,
), ),
); );
...@@ -325,7 +323,7 @@ class FakeDesktopDevice extends DesktopDevice { ...@@ -325,7 +323,7 @@ class FakeDesktopDevice extends DesktopDevice {
} }
} }
class FakeAppplicationPackage extends Fake implements ApplicationPackage {} class FakeApplicationPackage 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,7 +24,6 @@ import 'package:flutter_tools/src/convert.dart'; ...@@ -24,7 +24,6 @@ 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';
...@@ -2418,7 +2417,6 @@ void main() { ...@@ -2418,7 +2417,6 @@ 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;
...@@ -2453,7 +2451,6 @@ void main() { ...@@ -2453,7 +2451,6 @@ 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;
...@@ -2488,7 +2485,6 @@ void main() { ...@@ -2488,7 +2485,6 @@ 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,7 +4,6 @@ ...@@ -4,7 +4,6 @@
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';
...@@ -61,7 +60,7 @@ void main() { ...@@ -61,7 +60,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: !const LocalPlatform().isMacOS); // Only supported on macOS }, skip: true); // Extremely flaky due to https://github.com/flutter/flutter/issues/68144
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