blob: 2e04041488f872d4ee8d036e66bc4dcf250275aa [file] [log] [blame]
// 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 'package:file/memory.dart';
import 'package:platform/platform.dart';
import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.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/build_info.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:webdriver/sync_io.dart' as sync_io;
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
void main() {
group('drive', () {
DriveCommand command;
Device mockUnsupportedDevice;
MemoryFileSystem fs;
Directory tempDir;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
command = DriveCommand();
applyMocksToCommand(command);
fs = MemoryFileSystem();
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) {
throw 'Unexpected call to appStarter';
};
testRunner = (List<String> testArgs, Map<String, String> environment) {
throw 'Unexpected call to testRunner';
};
appStopper = (DriveCommand command) {
throw 'Unexpected call to appStopper';
};
});
tearDown(() {
command = null;
restoreExitFunction();
restoreAppStarter();
restoreAppStopper();
restoreTestRunner();
tryToDelete(tempDir);
});
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 = expectAsync2((DriveCommand command, Uri webUri) async => null);
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 0 when test ends successfully', () 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');
appStarter = expectAsync2((DriveCommand command, Uri webUri) async {
return LaunchResult.succeeded();
});
testRunner = expectAsync2((List<String> testArgs, Map<String, String> environment) async {
expect(testArgs, <String>[testFile]);
// VM_SERVICE_URL is not set by drive command arguments
expect(environment, <String, String>{
'VM_SERVICE_URL': 'null',
});
return null;
});
appStopper = expectAsync1((DriveCommand command) 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',
];
await createTestCommandRunner(command).run(args);
expect(testLogger.errorText, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns exitCode set by test runner', () 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');
appStarter = expectAsync2((DriveCommand command, Uri webUri) async {
return LaunchResult.succeeded();
});
testRunner = (List<String> testArgs, Map<String, String> environment) async {
throwToolExit(null, exitCode: 123);
};
appStopper = expectAsync1((DriveCommand command) 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(),
});
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();
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(), 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();
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();
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();
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();
testDeviceManager.addDevice(mockDevice);
final MockDeviceLogReader mockDeviceLogReader = MockDeviceLogReader();
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'),
)).thenAnswer((_) => Future<LaunchResult>.value(mockLaunchResult));
when(mockDevice.isAppInstalled(any)).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 = expectAsync1(
(DriveCommand command) 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,
));
}, 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,
));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('uses prebuilt app if --no-build arg provided', () async {
final Device mockDevice = await appStarterSetup();
final List<String> args = <String>[
'drive',
'--no-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: true,
));
}, 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();
testDeviceManager.addDevice(mockDevice);
final MockDeviceLogReader mockDeviceLogReader = MockDeviceLogReader();
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'),
)).thenAnswer((Invocation invocation) async {
debuggingOptions = invocation.namedArguments[#debuggingOptions] as DebuggingOptions;
return mockLaunchResult;
});
when(mockDevice.isAppInstalled(any))
.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 = expectAsync1(
(DriveCommand command) 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,
));
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,
);
});
});
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', () {
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',
],
'perfLoggingPrefs': <String, String>{
'traceCategories':
'devtools.timeline,'
'v8,blink.console,benchmark,blink,'
'blink.user_timing'
}
}
};
expect(getDesiredCapabilities(Browser.chrome, false), 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);
});
});
}
class MockDevice extends Mock implements Device {
MockDevice() {
when(isSupported()).thenReturn(true);
}
}
class MockAndroidDevice extends Mock implements AndroidDevice { }
class MockLaunchResult extends Mock implements LaunchResult { }