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 {
Set<String> get assetPathsToEvict => const <String>{};
@override
Uri get baseUri => null;
Uri get baseUri => _baseUri;
Uri _baseUri;
@override
Future<Uri> create() async {
......@@ -451,7 +452,8 @@ class WebDevFS implements DevFS {
entrypoint,
testMode: testMode,
);
return Uri.parse('http://$hostname:$port');
_baseUri = Uri.parse('http://$hostname:$port');
return _baseUri;
}
@override
......
......@@ -71,7 +71,7 @@ const String kExitMessage = 'Failed to establish connection with the applicatio
/// A hot-runner which handles browser specific delegation.
abstract class ResidentWebRunner extends ResidentRunner {
ResidentWebRunner(
this.device, {
FlutterDevice device, {
String target,
@required this.flutterProject,
@required bool ipv6,
......@@ -79,14 +79,14 @@ abstract class ResidentWebRunner extends ResidentRunner {
bool stayResident = true,
@required this.dartDefines,
}) : super(
<FlutterDevice>[],
<FlutterDevice>[device],
target: target ?? globals.fs.path.join('lib', 'main.dart'),
debuggingOptions: debuggingOptions,
ipv6: ipv6,
stayResident: stayResident,
);
final FlutterDevice device;
FlutterDevice get device => flutterDevices.first;
final FlutterProject flutterProject;
final List<String> dartDefines;
DateTime firstBuildTime;
......@@ -249,6 +249,12 @@ abstract class ResidentWebRunner extends ResidentRunner {
}
}
@override
Future<void> stopEchoingDeviceLog() async {
// Do nothing for ResidentWebRunner
await device.stopEchoingDeviceLog();
}
@override
Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
try {
......@@ -705,4 +711,10 @@ class _ResidentWebRunner extends ResidentWebRunner {
await cleanupAtFinish();
return 0;
}
@override
Future<void> exitApp() async {
await device.exitApps();
appFinished();
}
}
......@@ -22,6 +22,7 @@ import '../globals.dart' as globals;
import '../project.dart';
import '../resident_runner.dart';
import '../runner/flutter_command.dart' show FlutterCommandResult;
import '../web/web_runner.dart';
import 'run.dart';
/// Runs integration (a.k.a. end-to-end) tests.
......@@ -142,17 +143,11 @@ class DriveCommand extends RunCommandBase {
}
String observatoryUri;
ResidentRunner residentRunner;
final bool isWebPlatform = await device.targetPlatform == TargetPlatform.web_javascript;
if (argResults['use-existing-app'] == null) {
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) {
// This is because we need VM service to be able to drive the app.
// For Flutter Web, testing in release mode is allowed.
......@@ -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) {
throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1);
}
......@@ -243,6 +284,7 @@ $ex
}
throw Exception('Unable to run test: $error\n$stackTrace');
} finally {
await residentRunner?.exit();
await driver?.quit();
if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) {
globals.printStatus('Leaving the application running.');
......@@ -327,14 +369,14 @@ Future<Device> findTargetDevice() async {
}
/// 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)
void restoreAppStarter() {
appStarter = _startApp;
}
Future<LaunchResult> _startApp(DriveCommand command) async {
Future<LaunchResult> _startApp(DriveCommand command, Uri webUri) 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.');
......@@ -360,6 +402,15 @@ Future<LaunchResult> _startApp(DriveCommand command) async {
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.
......
......@@ -680,6 +680,13 @@ abstract class ResidentRunner {
bool get isRunningRelease => debuggingOptions.buildInfo.isRelease;
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()].
bool get exited => _exited;
......
......@@ -134,6 +134,8 @@ class ChromeDevice extends Device {
// See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
// for the web initialization and server logic.
final String url = platformArgs['uri'] as String;
final bool launchChrome = platformArgs['no-launch-chrome'] != true;
if (launchChrome) {
_chrome = await chromeLauncher.launch(
url,
dataDir: globals.fs.currentDirectory
......@@ -142,10 +144,10 @@ class ChromeDevice extends 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
......@@ -268,7 +270,7 @@ class WebServerDevice extends Device {
globals.printStatus('$mainPath is being served at $url', emphasis: true);
}
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
......
......@@ -45,7 +45,7 @@ void main() {
fs.file('pubspec.yaml')..createSync();
fs.file('.packages').createSync();
setExitFunctionForTests();
appStarter = (DriveCommand command) {
appStarter = (DriveCommand command, Uri webUri) {
throw 'Unexpected call to appStarter';
};
testRunner = (List<String> testArgs, Map<String, String> environment) {
......@@ -91,7 +91,7 @@ void main() {
testUsingContext('returns 1 when app fails to run', () async {
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 testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
......@@ -170,7 +170,7 @@ void main() {
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 = expectAsync1((DriveCommand command) async {
appStarter = expectAsync2((DriveCommand command, Uri webUri) async {
return LaunchResult.succeeded();
});
testRunner = expectAsync2((List<String> testArgs, Map<String, String> environment) async {
......@@ -207,7 +207,7 @@ void main() {
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 = expectAsync1((DriveCommand command) async {
appStarter = expectAsync2((DriveCommand command, Uri webUri) async {
return LaunchResult.succeeded();
});
testRunner = (List<String> testArgs, Map<String, String> environment) async {
......
......@@ -181,8 +181,10 @@ void main() {
FlutterDevice(mockDevice, 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(hotRunner.uri, mockDevFs.baseUri);
expect(result.isOk, true);
expect(result.message, isNot('hotRestart not supported'));
}, overrides: <Type, Generator>{
......@@ -216,8 +218,10 @@ void main() {
final List<FlutterDevice> devices = <FlutterDevice>[
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(hotRunner.uri, mockDevFs.baseUri);
expect(result.isOk, true);
expect(result.message, isNot('setupHotRestart failed'));
}, overrides: <Type, Generator>{
......
......@@ -119,6 +119,7 @@ void main() {
when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/');
when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS);
when(mockWebDevFS.sources).thenReturn(<Uri>[]);
when(mockWebDevFS.baseUri).thenReturn(Uri.parse('http://localhost:12345'));
when(mockFlutterDevice.generator).thenReturn(mockResidentCompiler);
when(mockChrome.chromeConnection).thenReturn(mockChromeConnection);
when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async {
......@@ -149,6 +150,7 @@ void main() {
}));
test('runner with web server device supports debugging with --start-paused', () => testbed.run(() {
_setupMocks();
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
......@@ -160,6 +162,7 @@ void main() {
urlTunneller: null,
);
expect(profileResidentWebRunner.uri, mockWebDevFS.baseUri);
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