Unverified Commit 5ee41472 authored by Angjie Li's avatar Angjie Li Committed by GitHub

Allow developers to run flutter driver web test directly (#51084)

parent 3f9d08a0
...@@ -438,7 +438,8 @@ class WebDevFS implements DevFS { ...@@ -438,7 +438,8 @@ class WebDevFS implements DevFS {
Set<String> get assetPathsToEvict => const <String>{}; Set<String> get assetPathsToEvict => const <String>{};
@override @override
Uri get baseUri => null; Uri get baseUri => _baseUri;
Uri _baseUri;
@override @override
Future<Uri> create() async { Future<Uri> create() async {
...@@ -451,7 +452,8 @@ class WebDevFS implements DevFS { ...@@ -451,7 +452,8 @@ class WebDevFS implements DevFS {
entrypoint, entrypoint,
testMode: testMode, testMode: testMode,
); );
return Uri.parse('http://$hostname:$port'); _baseUri = Uri.parse('http://$hostname:$port');
return _baseUri;
} }
@override @override
......
...@@ -71,7 +71,7 @@ const String kExitMessage = 'Failed to establish connection with the applicatio ...@@ -71,7 +71,7 @@ const String kExitMessage = 'Failed to establish connection with the applicatio
/// A hot-runner which handles browser specific delegation. /// A hot-runner which handles browser specific delegation.
abstract class ResidentWebRunner extends ResidentRunner { abstract class ResidentWebRunner extends ResidentRunner {
ResidentWebRunner( ResidentWebRunner(
this.device, { FlutterDevice device, {
String target, String target,
@required this.flutterProject, @required this.flutterProject,
@required bool ipv6, @required bool ipv6,
...@@ -79,14 +79,14 @@ abstract class ResidentWebRunner extends ResidentRunner { ...@@ -79,14 +79,14 @@ abstract class ResidentWebRunner extends ResidentRunner {
bool stayResident = true, bool stayResident = true,
@required this.dartDefines, @required this.dartDefines,
}) : super( }) : super(
<FlutterDevice>[], <FlutterDevice>[device],
target: target ?? globals.fs.path.join('lib', 'main.dart'), target: target ?? globals.fs.path.join('lib', 'main.dart'),
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
ipv6: ipv6, ipv6: ipv6,
stayResident: stayResident, stayResident: stayResident,
); );
final FlutterDevice device; FlutterDevice get device => flutterDevices.first;
final FlutterProject flutterProject; final FlutterProject flutterProject;
final List<String> dartDefines; final List<String> dartDefines;
DateTime firstBuildTime; DateTime firstBuildTime;
...@@ -249,6 +249,12 @@ abstract class ResidentWebRunner extends ResidentRunner { ...@@ -249,6 +249,12 @@ abstract class ResidentWebRunner extends ResidentRunner {
} }
} }
@override
Future<void> stopEchoingDeviceLog() async {
// Do nothing for ResidentWebRunner
await device.stopEchoingDeviceLog();
}
@override @override
Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async { Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
try { try {
...@@ -705,4 +711,10 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -705,4 +711,10 @@ class _ResidentWebRunner extends ResidentWebRunner {
await cleanupAtFinish(); await cleanupAtFinish();
return 0; return 0;
} }
@override
Future<void> exitApp() async {
await device.exitApps();
appFinished();
}
} }
...@@ -22,6 +22,7 @@ import '../globals.dart' as globals; ...@@ -22,6 +22,7 @@ import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
import '../resident_runner.dart'; import '../resident_runner.dart';
import '../runner/flutter_command.dart' show FlutterCommandResult; import '../runner/flutter_command.dart' show FlutterCommandResult;
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.
...@@ -142,17 +143,11 @@ class DriveCommand extends RunCommandBase { ...@@ -142,17 +143,11 @@ class DriveCommand extends RunCommandBase {
} }
String observatoryUri; String observatoryUri;
ResidentRunner residentRunner;
final bool isWebPlatform = await device.targetPlatform == TargetPlatform.web_javascript; final bool isWebPlatform = await device.targetPlatform == TargetPlatform.web_javascript;
if (argResults['use-existing-app'] == null) { if (argResults['use-existing-app'] == null) {
globals.printStatus('Starting application: $targetFile'); globals.printStatus('Starting application: $targetFile');
if (isWebPlatform) {
throwToolExit(
'Flutter Driver (web) does not support running without use-existing-app.\n'
'Please launch your application beforehand and connects via use-existing-app.'
);
}
if (getBuildInfo().isRelease && !isWebPlatform) { if (getBuildInfo().isRelease && !isWebPlatform) {
// This is because we need VM service to be able to drive the app. // This is because we need VM service to be able to drive the app.
// For Flutter Web, testing in release mode is allowed. // For Flutter Web, testing in release mode is allowed.
...@@ -164,7 +159,53 @@ class DriveCommand extends RunCommandBase { ...@@ -164,7 +159,53 @@ class DriveCommand extends RunCommandBase {
); );
} }
final LaunchResult result = await appStarter(this); if (isWebPlatform && getBuildInfo().isDebug) {
// TODO(angjieli): remove this once running against
// target under test_driver in debug mode is supported
throwToolExit(
'Flutter Driver web does not support running in debug mode.\n'
'\n'
'Use --profile mode for testing application performance.\n'
'Use --release 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,
flutterProject: flutterProject,
trackWidgetCreation: boolArg('track-widget-creation'),
target: targetFile,
buildMode: getBuildMode()
);
residentRunner = webRunnerFactory.createWebRunner(
flutterDevice,
target: targetFile,
flutterProject: flutterProject,
ipv6: ipv6,
debuggingOptions: DebuggingOptions.enabled(getBuildInfo()),
stayResident: false,
dartDefines: dartDefines,
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;
}
final LaunchResult result = await appStarter(this, webUri);
if (result == null) { if (result == null) {
throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1); throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1);
} }
...@@ -243,6 +284,7 @@ $ex ...@@ -243,6 +284,7 @@ $ex
} }
throw Exception('Unable to run test: $error\n$stackTrace'); throw Exception('Unable to run test: $error\n$stackTrace');
} finally { } finally {
await residentRunner?.exit();
await driver?.quit(); await driver?.quit();
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.'); globals.printStatus('Leaving the application running.');
...@@ -327,14 +369,14 @@ Future<Device> findTargetDevice() async { ...@@ -327,14 +369,14 @@ Future<Device> findTargetDevice() async {
} }
/// Starts the application on the device given command configuration. /// Starts the application on the device given command configuration.
typedef AppStarter = Future<LaunchResult> Function(DriveCommand command); typedef AppStarter = Future<LaunchResult> Function(DriveCommand command, Uri webUri);
AppStarter appStarter = _startApp; // (mutable for testing) AppStarter appStarter = _startApp; // (mutable for testing)
void restoreAppStarter() { void restoreAppStarter() {
appStarter = _startApp; appStarter = _startApp;
} }
Future<LaunchResult> _startApp(DriveCommand command) async { Future<LaunchResult> _startApp(DriveCommand command, Uri webUri) async {
final String mainPath = findMainDartFile(command.targetFile); final String mainPath = findMainDartFile(command.targetFile);
if (await globals.fs.type(mainPath) != FileSystemEntityType.file) { if (await globals.fs.type(mainPath) != FileSystemEntityType.file) {
globals.printError('Tried to run $mainPath, but that file does not exist.'); globals.printError('Tried to run $mainPath, but that file does not exist.');
...@@ -360,6 +402,15 @@ Future<LaunchResult> _startApp(DriveCommand command) async { ...@@ -360,6 +402,15 @@ Future<LaunchResult> _startApp(DriveCommand command) async {
platformArgs['trace-startup'] = 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.'); globals.printTrace('Starting application.');
// Forward device log messages to the terminal window running the "drive" command. // Forward device log messages to the terminal window running the "drive" command.
......
...@@ -680,6 +680,13 @@ abstract class ResidentRunner { ...@@ -680,6 +680,13 @@ abstract class ResidentRunner {
bool get isRunningRelease => debuggingOptions.buildInfo.isRelease; bool get isRunningRelease => debuggingOptions.buildInfo.isRelease;
bool get supportsServiceProtocol => isRunningDebug || isRunningProfile; bool get supportsServiceProtocol => isRunningDebug || isRunningProfile;
// Returns the Uri of the first connected device for mobile,
// and only connected device for web.
//
// Would be null if there is no device connected or
// there is no devFS associated with the first device.
Uri get uri => flutterDevices.first?.devFS?.baseUri;
/// Returns [true] if the resident runner exited after invoking [exit()]. /// Returns [true] if the resident runner exited after invoking [exit()].
bool get exited => _exited; bool get exited => _exited;
......
...@@ -134,18 +134,20 @@ class ChromeDevice extends Device { ...@@ -134,18 +134,20 @@ class ChromeDevice extends Device {
// See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart // See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
// for the web initialization and server logic. // for the web initialization and server logic.
final String url = platformArgs['uri'] as String; final String url = platformArgs['uri'] as String;
_chrome = await chromeLauncher.launch( final bool launchChrome = platformArgs['no-launch-chrome'] != true;
url, if (launchChrome) {
dataDir: globals.fs.currentDirectory _chrome = await chromeLauncher.launch(
.childDirectory('.dart_tool') url,
.childDirectory('chrome-device'), dataDir: globals.fs.currentDirectory
headless: debuggingOptions.webRunHeadless, .childDirectory('.dart_tool')
debugPort: debuggingOptions.webBrowserDebugPort, .childDirectory('chrome-device'),
); headless: debuggingOptions.webRunHeadless,
debugPort: debuggingOptions.webBrowserDebugPort,
globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': true}); );
}
return LaunchResult.succeeded(observatoryUri: null);
globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': launchChrome});
return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null);
} }
@override @override
...@@ -268,7 +270,7 @@ class WebServerDevice extends Device { ...@@ -268,7 +270,7 @@ class WebServerDevice extends Device {
globals.printStatus('$mainPath is being served at $url', emphasis: true); globals.printStatus('$mainPath is being served at $url', emphasis: true);
} }
globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': false}); globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': false});
return LaunchResult.succeeded(observatoryUri: null); return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null);
} }
@override @override
......
...@@ -45,7 +45,7 @@ void main() { ...@@ -45,7 +45,7 @@ void main() {
fs.file('pubspec.yaml')..createSync(); fs.file('pubspec.yaml')..createSync();
fs.file('.packages').createSync(); fs.file('.packages').createSync();
setExitFunctionForTests(); setExitFunctionForTests();
appStarter = (DriveCommand command) { appStarter = (DriveCommand command, Uri webUri) {
throw 'Unexpected call to appStarter'; throw 'Unexpected call to appStarter';
}; };
testRunner = (List<String> testArgs, Map<String, String> environment) { testRunner = (List<String> testArgs, Map<String, String> environment) {
...@@ -91,7 +91,7 @@ void main() { ...@@ -91,7 +91,7 @@ void main() {
testUsingContext('returns 1 when app fails to run', () async { testUsingContext('returns 1 when app fails to run', () async {
testDeviceManager.addDevice(MockDevice()); testDeviceManager.addDevice(MockDevice());
appStarter = expectAsync1((DriveCommand command) async => null); appStarter = expectAsync2((DriveCommand command, Uri webUri) async => null);
final String testApp = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e.dart'); 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 String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
...@@ -170,7 +170,7 @@ void main() { ...@@ -170,7 +170,7 @@ void main() {
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); 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'); final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
appStarter = expectAsync1((DriveCommand command) async { appStarter = expectAsync2((DriveCommand command, Uri webUri) async {
return LaunchResult.succeeded(); return LaunchResult.succeeded();
}); });
testRunner = expectAsync2((List<String> testArgs, Map<String, String> environment) async { testRunner = expectAsync2((List<String> testArgs, Map<String, String> environment) async {
...@@ -207,7 +207,7 @@ void main() { ...@@ -207,7 +207,7 @@ void main() {
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); 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'); final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
appStarter = expectAsync1((DriveCommand command) async { appStarter = expectAsync2((DriveCommand command, Uri webUri) async {
return LaunchResult.succeeded(); return LaunchResult.succeeded();
}); });
testRunner = (List<String> testArgs, Map<String, String> environment) async { testRunner = (List<String> testArgs, Map<String, String> environment) async {
......
...@@ -181,8 +181,10 @@ void main() { ...@@ -181,8 +181,10 @@ void main() {
FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs, FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs,
FlutterDevice(mockHotDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs, FlutterDevice(mockHotDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs,
]; ];
final OperationResult result = await HotRunner(devices).restart(fullRestart: true); final HotRunner hotRunner = HotRunner(devices);
final OperationResult result = await hotRunner.restart(fullRestart: true);
// Expect hot restart was successful. // Expect hot restart was successful.
expect(hotRunner.uri, mockDevFs.baseUri);
expect(result.isOk, true); expect(result.isOk, true);
expect(result.message, isNot('hotRestart not supported')); expect(result.message, isNot('hotRestart not supported'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -216,8 +218,10 @@ void main() { ...@@ -216,8 +218,10 @@ void main() {
final List<FlutterDevice> devices = <FlutterDevice>[ final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs, FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs,
]; ];
final OperationResult result = await HotRunner(devices).restart(fullRestart: true); final HotRunner hotRunner = HotRunner(devices);
final OperationResult result = await hotRunner.restart(fullRestart: true);
// Expect hot restart successful. // Expect hot restart successful.
expect(hotRunner.uri, mockDevFs.baseUri);
expect(result.isOk, true); expect(result.isOk, true);
expect(result.message, isNot('setupHotRestart failed')); expect(result.message, isNot('setupHotRestart failed'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
......
...@@ -119,6 +119,7 @@ void main() { ...@@ -119,6 +119,7 @@ void main() {
when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/'); when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/');
when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS); when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS);
when(mockWebDevFS.sources).thenReturn(<Uri>[]); when(mockWebDevFS.sources).thenReturn(<Uri>[]);
when(mockWebDevFS.baseUri).thenReturn(Uri.parse('http://localhost:12345'));
when(mockFlutterDevice.generator).thenReturn(mockResidentCompiler); when(mockFlutterDevice.generator).thenReturn(mockResidentCompiler);
when(mockChrome.chromeConnection).thenReturn(mockChromeConnection); when(mockChrome.chromeConnection).thenReturn(mockChromeConnection);
when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async { when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async {
...@@ -149,6 +150,7 @@ void main() { ...@@ -149,6 +150,7 @@ void main() {
})); }));
test('runner with web server device supports debugging with --start-paused', () => testbed.run(() { test('runner with web server device supports debugging with --start-paused', () => testbed.run(() {
_setupMocks();
when(mockFlutterDevice.device).thenReturn(WebServerDevice()); when(mockFlutterDevice.device).thenReturn(WebServerDevice());
final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice, mockFlutterDevice,
...@@ -160,6 +162,7 @@ void main() { ...@@ -160,6 +162,7 @@ void main() {
urlTunneller: null, urlTunneller: null,
); );
expect(profileResidentWebRunner.uri, mockWebDevFS.baseUri);
expect(profileResidentWebRunner.debuggingEnabled, true); expect(profileResidentWebRunner.debuggingEnabled, true);
})); }));
......
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