Reverts "Reapply "Make device debuggable if useDwdsWebSocketConnection is true … (#173551)" (#173568)" (#173587)

<!-- start_original_pr_link -->
Reverts: flutter/flutter#173568
<!-- end_original_pr_link -->
<!-- start_initiating_author -->
Initiated by: bkonyi
<!-- end_initiating_author -->
<!-- start_revert_reason -->
Reason for reverting: Chrome isn't configured for the test configuration
and is causing failures.
<!-- end_revert_reason -->
<!-- start_original_pr_author -->
Original PR Author: bkonyi
<!-- end_original_pr_author -->

<!-- start_reviewers -->
Reviewed By: {jyameo, matanlurey}
<!-- end_reviewers -->

<!-- start_revert_body -->
This change reverts the following previous change:
This reverts commit e2a347b14a18729dd24c2cf3240ef22562195e2d.

Previously reverted due to `*_chrome_dev_mode` tests failing on all
platforms.
<!-- end_revert_body -->

Co-authored-by: auto-submit[bot] <flutter-engprod-team@google.com>
This commit is contained in:
auto-submit[bot] 2025-08-11 22:15:48 +00:00 committed by GitHub
parent 1fcb4796c4
commit 17c92b7ba6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 279 additions and 574 deletions

View File

@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
Future<void> main() async {
await task(createWebDevModeTest());
await task(createWebDevModeTest(WebDevice.webServer, false));
}

View File

@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
Future<void> main() async {
await task(createWebDevModeTest());
await task(createWebDevModeTest(WebDevice.webServer, false));
}

View File

@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
Future<void> main() async {
await task(createWebDevModeTest());
await task(createWebDevModeTest(WebDevice.webServer, false));
}

View File

@ -26,7 +26,6 @@ class ChromeOptions {
this.headless,
this.debugPort,
this.enableWasmGC = false,
this.silent = false,
});
/// If not null passed as `--user-data-dir`.
@ -58,9 +57,6 @@ class ChromeOptions {
/// Whether to enable experimental WasmGC flags
final bool enableWasmGC;
/// Disables Chrome stdio outputs.
final bool silent;
}
/// A function called when the Chrome process encounters an error.
@ -123,7 +119,6 @@ class Chrome {
final io.Process chromeProcess = await _spawnChromiumProcess(
_findSystemChromeExecutable(),
args,
silent: options.silent,
workingDirectory: workingDirectory,
);
@ -615,7 +610,6 @@ const String _kGlibcError = 'Inconsistency detected by ld.so';
Future<io.Process> _spawnChromiumProcess(
String executable,
List<String> args, {
required bool silent,
String? workingDirectory,
}) async {
// Keep attempting to launch the browser until one of:
@ -629,9 +623,7 @@ Future<io.Process> _spawnChromiumProcess(
);
process.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((String line) {
if (!silent) {
print('[CHROME STDOUT]: $line');
}
print('[CHROME STDOUT]: $line');
});
// Wait until the DevTools are listening before trying to connect. This is
@ -641,9 +633,7 @@ Future<io.Process> _spawnChromiumProcess(
.transform(utf8.decoder)
.transform(const LineSplitter())
.map((String line) {
if (!silent) {
print('[CHROME STDERR]:$line');
}
print('[CHROME STDERR]:$line');
if (line.contains(_kGlibcError)) {
hitGlibcBug = true;
}

View File

@ -56,7 +56,6 @@ enum DeviceOperatingSystem {
ios,
linux,
macos,
webServer,
windows,
}
@ -81,8 +80,6 @@ abstract class DeviceDiscovery {
return LinuxDeviceDiscovery();
case DeviceOperatingSystem.macos:
return MacosDeviceDiscovery();
case DeviceOperatingSystem.webServer:
return WebServerDeviceDiscovery();
case DeviceOperatingSystem.windows:
return WindowsDeviceDiscovery();
case DeviceOperatingSystem.fake:
@ -434,40 +431,6 @@ class MacosDeviceDiscovery implements DeviceDiscovery {
Future<Device> get workingDevice async => _device;
}
class WebServerDeviceDiscovery implements DeviceDiscovery {
factory WebServerDeviceDiscovery() {
return _instance ??= WebServerDeviceDiscovery._();
}
WebServerDeviceDiscovery._();
static WebServerDeviceDiscovery? _instance;
static const WebServerDevice _device = WebServerDevice();
@override
Future<Map<String, HealthCheckResult>> checkDevices() async {
return <String, HealthCheckResult>{};
}
@override
Future<void> chooseWorkingDevice() async {}
@override
Future<void> chooseWorkingDeviceById(String deviceId) async {}
@override
Future<List<String>> discoverDevices() async {
return <String>['web-server'];
}
@override
Future<void> performPreflightTasks() async {}
@override
Future<Device> get workingDevice async => _device;
}
class WindowsDeviceDiscovery implements DeviceDiscovery {
factory WindowsDeviceDiscovery() {
return _instance ??= WindowsDeviceDiscovery._();
@ -1297,61 +1260,6 @@ class MacosDevice extends Device {
Future<void> awaitDevice() async {}
}
class WebServerDevice extends Device {
const WebServerDevice();
@override
String get deviceId => 'web-server';
@override
Future<Map<String, dynamic>> getMemoryStats(String packageName) async {
return <String, dynamic>{};
}
@override
Future<void> home() async {}
@override
Future<bool> isAsleep() async {
return false;
}
@override
Future<bool> isAwake() async {
return true;
}
@override
Stream<String> get logcat => const Stream<String>.empty();
@override
Future<void> clearLogs() async {}
@override
Future<void> reboot() async {}
@override
Future<void> sendToSleep() async {}
@override
Future<void> stop(String packageName) async {}
@override
Future<void> tap(int x, int y) async {}
@override
Future<void> togglePower() async {}
@override
Future<void> unlock() async {}
@override
Future<void> wakeUp() async {}
@override
Future<void> awaitDevice() async {}
}
class WindowsDevice extends Device {
const WindowsDevice();

View File

@ -1002,8 +1002,6 @@ class StartupTest {
);
final String buildRoot = path.join(testDirectory, 'build');
applicationBinaryPath = _findDarwinAppInBuildDirectory(buildRoot);
case DeviceOperatingSystem.webServer:
break;
case DeviceOperatingSystem.windows:
await flutter(
'build',
@ -1155,7 +1153,6 @@ class DevtoolsStartupTest {
case DeviceOperatingSystem.fuchsia:
case DeviceOperatingSystem.linux:
case DeviceOperatingSystem.macos:
case DeviceOperatingSystem.webServer:
case DeviceOperatingSystem.windows:
break;
}
@ -1496,7 +1493,6 @@ class PerfTest {
case DeviceOperatingSystem.fuchsia:
case DeviceOperatingSystem.linux:
case DeviceOperatingSystem.macos:
case DeviceOperatingSystem.webServer:
case DeviceOperatingSystem.windows:
recordGPU = false;
}
@ -2015,8 +2011,6 @@ class CompileTest {
throw Exception('Unsupported option for Fuchsia devices');
case DeviceOperatingSystem.linux:
throw Exception('Unsupported option for Linux devices');
case DeviceOperatingSystem.webServer:
throw Exception('Unsupported option for Web Server devices');
case DeviceOperatingSystem.windows:
unawaited(stderr.flush());
options.insert(0, 'windows');
@ -2084,8 +2078,6 @@ class CompileTest {
case DeviceOperatingSystem.macos:
unawaited(stderr.flush());
options.insert(0, 'macos');
case DeviceOperatingSystem.webServer:
throw Exception('Unsupported option for Web Server devices');
case DeviceOperatingSystem.windows:
unawaited(stderr.flush());
options.insert(0, 'windows');
@ -2118,7 +2110,6 @@ class CompileTest {
case DeviceOperatingSystem.fake:
case DeviceOperatingSystem.fuchsia:
case DeviceOperatingSystem.linux:
case DeviceOperatingSystem.webServer:
case DeviceOperatingSystem.windows:
throw Exception('Called ${CompileTest.getSizesFromDarwinApp} with $operatingSystem.');
}

View File

@ -9,8 +9,6 @@ import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:yaml_edit/yaml_edit.dart';
import '../framework/browser.dart';
import '../framework/devices.dart';
import '../framework/framework.dart';
import '../framework/task_result.dart';
import '../framework/utils.dart';
@ -33,123 +31,25 @@ const String kFirstRecompileTime = 'FirstRecompileTime';
const String kSecondStartupTime = 'SecondStartupTime';
const String kSecondRestartTime = 'SecondRestartTime';
const String kWebServerDevice = 'web-server';
final RegExp servedAtPattern = RegExp('is being served at (.*)');
int hotRestartCount = 0;
Future<List<int>> launch({required bool isFirstRun}) async {
final List<String> options = <String>[
'--hot',
'-d',
kWebServerDevice,
'--verbose',
'--resident',
'--target=lib/main.dart',
];
final Process process = await startFlutter('run', options: options);
final List<int> measurements = <int>[];
final Completer<void> stdoutDone = Completer<void>();
final Completer<void> stderrDone = Completer<void>();
final Completer<void> waitForService = Completer<void>();
final Stopwatch sw = Stopwatch()..start();
Chrome? chrome;
bool restarted = false;
process
..stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(
(String line) async {
if (line.contains(servedAtPattern)) {
final String url = servedAtPattern.firstMatch(line)!.group(1)!;
chrome = await Chrome.launch(
ChromeOptions(url: url, headless: true, silent: true, debugPort: 10000),
onError: (String e) {},
);
}
if (line.contains('DevHandler: Debug service listening on')) {
waitForService.complete();
}
// non-dwds builds do not know when the browser is loaded so keep trying
// until this succeeds.
if (line.contains('Ignoring terminal input')) {
unawaited(
Future<void>.delayed(const Duration(seconds: 1)).then((void _) {
process.stdin.write(restarted ? 'q' : 'r');
}),
);
return;
}
if (line.contains('Hot restart')) {
unawaited(
waitForService.future.then((_) {
// measure clean start-up time.
sw.stop();
measurements.add(sw.elapsedMilliseconds);
sw
..reset()
..start();
process.stdin.write('r');
}),
);
return;
}
if (line.contains('Reloaded application')) {
if (hotRestartCount == 0) {
assert(isFirstRun);
measurements.add(sw.elapsedMilliseconds);
// Update the file and reload again.
final File appDartSource = file(
path.join(_editedFlutterGalleryDir.path, 'lib/gallery/app.dart'),
);
appDartSource.writeAsStringSync(
appDartSource.readAsStringSync().replaceFirst(
"'Flutter Gallery'",
"'Updated Flutter Gallery'",
),
);
sw
..reset()
..start();
process.stdin.writeln('r');
++hotRestartCount;
} else {
restarted = true;
measurements.add(sw.elapsedMilliseconds);
// Quit after second hot restart.
process.stdin.writeln('q');
}
}
print('stdout: $line');
},
onDone: () {
stdoutDone.complete();
},
)
..stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(
(String line) {
print('stderr: $line');
},
onDone: () {
stderrDone.complete();
},
);
await Future.wait<void>(<Future<void>>[stdoutDone.future, stderrDone.future]);
await process.exitCode;
chrome?.stop();
return measurements;
abstract class WebDevice {
static const String chrome = 'chrome';
static const String webServer = 'web-server';
}
TaskFunction createWebDevModeTest() {
deviceOperatingSystem = DeviceOperatingSystem.webServer;
TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompiler) {
return () async {
final List<String> options = <String>[
'--hot',
'-d',
webDevice,
'--verbose',
'--resident',
'--target=lib/main.dart',
];
int hotRestartCount = 0;
final String expectedMessage = webDevice == WebDevice.webServer
? 'Recompile complete'
: 'Reloaded application';
final Map<String, int> measurements = <String, int>{};
await inDirectory<void>(flutterDirectory, () async {
rmTree(_editedFlutterGalleryDir);
@ -166,20 +66,139 @@ TaskFunction createWebDevModeTest() {
).writeAsStringSync(yamlEditor.toString());
await inDirectory<void>(_editedFlutterGalleryDir, () async {
await flutter('packages', options: <String>['get']);
final List<int> firstMeasurements = await launch(isFirstRun: true);
measurements.addAll(<String, int>{
kInitialStartupTime: firstMeasurements[0],
kFirstRecompileTime: firstMeasurements[1],
kFirstRestartTime: firstMeasurements[2],
});
{
await flutter('packages', options: <String>['get']);
final Process process = await startFlutter('run', options: options);
final Completer<void> stdoutDone = Completer<void>();
final Completer<void> stderrDone = Completer<void>();
final Stopwatch sw = Stopwatch()..start();
bool restarted = false;
process.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(
(String line) {
// non-dwds builds do not know when the browser is loaded so keep trying
// until this succeeds.
if (line.contains('Ignoring terminal input')) {
Future<void>.delayed(const Duration(seconds: 1)).then((void _) {
process.stdin.write(restarted ? 'q' : 'r');
});
return;
}
if (line.contains('Hot restart')) {
// measure clean start-up time.
sw.stop();
measurements[kInitialStartupTime] = sw.elapsedMilliseconds;
sw
..reset()
..start();
process.stdin.write('r');
return;
}
if (line.contains(expectedMessage)) {
if (hotRestartCount == 0) {
measurements[kFirstRestartTime] = sw.elapsedMilliseconds;
// Update the file and reload again.
final File appDartSource = file(
path.join(_editedFlutterGalleryDir.path, 'lib/gallery/app.dart'),
);
appDartSource.writeAsStringSync(
appDartSource.readAsStringSync().replaceFirst(
"'Flutter Gallery'",
"'Updated Flutter Gallery'",
),
);
sw
..reset()
..start();
process.stdin.writeln('r');
++hotRestartCount;
} else {
restarted = true;
measurements[kFirstRecompileTime] = sw.elapsedMilliseconds;
// Quit after second hot restart.
process.stdin.writeln('q');
}
}
print('stdout: $line');
},
onDone: () {
stdoutDone.complete();
},
);
process.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(
(String line) {
print('stderr: $line');
},
onDone: () {
stderrDone.complete();
},
);
await Future.wait<void>(<Future<void>>[stdoutDone.future, stderrDone.future]);
await process.exitCode;
}
// Start `flutter run` again to make sure it loads from the previous
// state. dev compilers loads up from previously compiled JavaScript.
final List<int> secondMeasurements = await launch(isFirstRun: false);
measurements.addAll(<String, int>{
kSecondStartupTime: secondMeasurements[0],
kSecondRestartTime: secondMeasurements[1],
});
{
final Stopwatch sw = Stopwatch()..start();
final Process process = await startFlutter('run', options: options);
final Completer<void> stdoutDone = Completer<void>();
final Completer<void> stderrDone = Completer<void>();
bool restarted = false;
process.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(
(String line) {
// non-dwds builds do not know when the browser is loaded so keep trying
// until this succeeds.
if (line.contains('Ignoring terminal input')) {
Future<void>.delayed(const Duration(seconds: 1)).then((void _) {
process.stdin.write(restarted ? 'q' : 'r');
});
return;
}
if (line.contains('Hot restart')) {
measurements[kSecondStartupTime] = sw.elapsedMilliseconds;
sw
..reset()
..start();
process.stdin.write('r');
return;
}
if (line.contains(expectedMessage)) {
restarted = true;
measurements[kSecondRestartTime] = sw.elapsedMilliseconds;
process.stdin.writeln('q');
}
print('stdout: $line');
},
onDone: () {
stdoutDone.complete();
},
);
process.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(
(String line) {
print('stderr: $line');
},
onDone: () {
stderrDone.complete();
},
);
await Future.wait<void>(<Future<void>>[stdoutDone.future, stderrDone.future]);
await process.exitCode;
}
});
});
if (hotRestartCount != 1) {

View File

@ -152,22 +152,21 @@ class ResidentWebRunner extends ResidentRunner {
// Only non-wasm debug builds of the web support the service protocol.
@override
late final bool supportsServiceProtocol =
!debuggingOptions.webUseWasm && isRunningDebug && _deviceIsDebuggable;
bool get supportsServiceProtocol =>
!debuggingOptions.webUseWasm && isRunningDebug && deviceIsDebuggable;
/// Device is debuggable if not a WebServer device, or if running with
/// --start-paused or using DWDS WebSocket connection (WebServer device).
late final bool _deviceIsDebuggable =
device!.device is! WebServerDevice ||
debuggingOptions.startPaused ||
useDwdsWebSocketConnection;
@override
bool get debuggingEnabled => isRunningDebug && deviceIsDebuggable;
late final useDwdsWebSocketConnection = device!.device is! ChromiumDevice;
/// WebServer device is debuggable when running with --start-paused.
bool get deviceIsDebuggable => device!.device is! WebServerDevice || debuggingOptions.startPaused;
@override
// Web uses a different plugin registry.
bool get generateDartPluginRegistry => false;
bool get _enableDwds => debuggingEnabled;
@override
bool get reloadIsRestart =>
debuggingOptions.webUseWasm ||
@ -287,6 +286,24 @@ class ResidentWebRunner extends ResidentRunner {
? WebExpressionCompiler(device!.generator!, fileSystem: _fileSystem)
: null;
// Retrieve connected web devices, excluding the web server device.
final List<Device>? devices = await globals.deviceManager?.getAllDevices();
final nonWebServerConnectedDeviceIds = <String>{
for (final Device d in devices!.where(
(Device d) =>
d.platformType == PlatformType.web &&
d.isConnected &&
d.id != WebServerDevice.kWebServerDeviceId,
))
d.id,
};
// Use Chrome-based connection only if we have a connected ChromiumDevice
// Otherwise, use DWDS WebSocket connection
final bool useDwdsWebSocketConnection =
!(_chromiumLauncher != null &&
nonWebServerConnectedDeviceIds.contains(device!.device!.id));
device!.devFS = WebDevFS(
webDevServerConfig: updatedConfig,
packagesFilePath: packagesFilePath,
@ -295,7 +312,7 @@ class ResidentWebRunner extends ResidentRunner {
useSseForDebugBackend: debuggingOptions.webUseSseForDebugBackend,
useSseForInjectedClient: debuggingOptions.webUseSseForInjectedClient,
buildInfo: debuggingOptions.buildInfo,
enableDwds: supportsServiceProtocol,
enableDwds: _enableDwds,
enableDds: debuggingOptions.enableDds,
entrypoint: _fileSystem.file(target).uri,
expressionCompiler: expressionCompiler,
@ -489,7 +506,7 @@ class ResidentWebRunner extends ResidentRunner {
Duration? reloadDuration;
Duration? reassembleDuration;
try {
if (!_deviceIsDebuggable) {
if (!deviceIsDebuggable) {
_logger.printStatus('Recompile complete. Page requires refresh.');
} else if (isRunningDebug) {
if (fullRestart) {
@ -558,7 +575,7 @@ class ResidentWebRunner extends ResidentRunner {
}
// Don't track restart times for dart2js builds or web-server devices.
if (debuggingOptions.buildInfo.isDebug && _deviceIsDebuggable) {
if (debuggingOptions.buildInfo.isDebug && deviceIsDebuggable) {
if (fullRestart) {
_analytics.send(
Event.timing(
@ -773,102 +790,86 @@ class ResidentWebRunner extends ResidentRunner {
Uri? websocketUri;
if (supportsServiceProtocol) {
assert(connectDebug != null);
unawaited(
connectDebug!.then((connectionResult) async {
_connectionResult = connectionResult;
unawaited(_connectionResult!.debugConnection!.onDone.whenComplete(_cleanupAndExit));
_connectionResult = await connectDebug;
unawaited(_connectionResult!.debugConnection!.onDone.whenComplete(_cleanupAndExit));
void onLogEvent(vmservice.Event event) {
final String message = processVmServiceMessage(event);
_logger.printStatus(message);
}
void onLogEvent(vmservice.Event event) {
final String message = processVmServiceMessage(event);
_logger.printStatus(message);
}
// This flag is needed to manage breakpoints properly.
if (debuggingOptions.startPaused && debuggingOptions.debuggingEnabled) {
try {
final vmservice.Response result = await _vmService.service.setFlag(
'pause_isolates_on_start',
'true',
);
if (result is! vmservice.Success) {
_logger.printError('setFlag failure: $result');
}
} on Exception catch (e) {
_logger.printError(
'Failed to set pause_isolates_on_start=true, proceeding. '
'Error: $e',
);
}
}
_stdOutSub = _vmService.service.onStdoutEvent.listen(onLogEvent);
_stdErrSub = _vmService.service.onStderrEvent.listen(onLogEvent);
_serviceSub = _vmService.service.onServiceEvent.listen(_onServiceEvent);
try {
await _vmService.service.streamListen(vmservice.EventStreams.kStdout);
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
try {
await _vmService.service.streamListen(vmservice.EventStreams.kStderr);
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
try {
await _vmService.service.streamListen(vmservice.EventStreams.kService);
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
try {
await _vmService.service.streamListen(vmservice.EventStreams.kIsolate);
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're not already subscribed.
}
await setUpVmService(
reloadSources: (String isolateId, {bool? force, bool? pause}) async {
await restart(pause: pause);
},
device: device!.device,
flutterProject: flutterProject,
printStructuredErrorLogMethod: printStructuredErrorLog,
vmService: _vmService.service,
// This flag is needed to manage breakpoints properly.
if (debuggingOptions.startPaused && debuggingOptions.debuggingEnabled) {
try {
final vmservice.Response result = await _vmService.service.setFlag(
'pause_isolates_on_start',
'true',
);
websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri);
device!.vmService = _vmService;
// Run main immediately if the app is not started paused or if there
// is no debugger attached. Otherwise, runMain when a resume event
// is received.
if (!debuggingOptions.startPaused || !supportsServiceProtocol) {
_connectionResult!.appConnection!.runMain();
} else {
late StreamSubscription<void> resumeSub;
resumeSub = _vmService.service.onDebugEvent.listen((vmservice.Event event) {
if (event.type == vmservice.EventKind.kResume) {
_connectionResult!.appConnection!.runMain();
resumeSub.cancel();
}
});
if (result is! vmservice.Success) {
_logger.printError('setFlag failure: $result');
}
} on Exception catch (e) {
_logger.printError(
'Failed to set pause_isolates_on_start=true, proceeding. '
'Error: $e',
);
}
}
if (websocketUri != null) {
if (debuggingOptions.vmserviceOutFile != null) {
_fileSystem.file(debuggingOptions.vmserviceOutFile)
..createSync(recursive: true)
..writeAsStringSync(websocketUri.toString());
}
_logger.printStatus('Debug service listening on $websocketUri');
}
connectionInfoCompleter?.complete(DebugConnectionInfo(wsUri: websocketUri));
}),
_stdOutSub = _vmService.service.onStdoutEvent.listen(onLogEvent);
_stdErrSub = _vmService.service.onStderrEvent.listen(onLogEvent);
_serviceSub = _vmService.service.onServiceEvent.listen(_onServiceEvent);
try {
await _vmService.service.streamListen(vmservice.EventStreams.kStdout);
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
try {
await _vmService.service.streamListen(vmservice.EventStreams.kStderr);
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
try {
await _vmService.service.streamListen(vmservice.EventStreams.kService);
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
try {
await _vmService.service.streamListen(vmservice.EventStreams.kIsolate);
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're not already subscribed.
}
await setUpVmService(
reloadSources: (String isolateId, {bool? force, bool? pause}) async {
await restart(pause: pause);
},
device: device!.device,
flutterProject: flutterProject,
printStructuredErrorLogMethod: printStructuredErrorLog,
vmService: _vmService.service,
);
} else {
connectionInfoCompleter?.complete(DebugConnectionInfo());
websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri);
device!.vmService = _vmService;
// Run main immediately if the app is not started paused or if there
// is no debugger attached. Otherwise, runMain when a resume event
// is received.
if (!debuggingOptions.startPaused || !supportsServiceProtocol) {
_connectionResult!.appConnection!.runMain();
} else {
late StreamSubscription<void> resumeSub;
resumeSub = _vmService.service.onDebugEvent.listen((vmservice.Event event) {
if (event.type == vmservice.EventKind.kResume) {
_connectionResult!.appConnection!.runMain();
resumeSub.cancel();
}
});
}
}
// TODO(bkonyi): remove when ready to serve DevTools from DDS.
if (debuggingOptions.enableDevTools) {
@ -880,8 +881,16 @@ class ResidentWebRunner extends ResidentRunner {
),
);
}
if (websocketUri != null) {
if (debuggingOptions.vmserviceOutFile != null) {
_fileSystem.file(debuggingOptions.vmserviceOutFile)
..createSync(recursive: true)
..writeAsStringSync(websocketUri.toString());
}
_logger.printStatus('Debug service listening on $websocketUri');
}
appStartedCompleter?.complete();
connectionInfoCompleter?.complete(DebugConnectionInfo(wsUri: websocketUri));
if (stayResident) {
await waitForAppToFinish();
} else {

View File

@ -13,7 +13,7 @@ dependencies:
archive: 3.6.1
args: 2.7.0
dds: 5.0.3
dwds: 24.4.1
dwds: 24.4.0
code_builder: 4.10.1
collection: 1.19.1
completion: 1.0.2
@ -126,4 +126,4 @@ dev_dependencies:
dartdoc:
# Exclude this package from the hosted API docs.
nodoc: true
# PUBSPEC CHECKSUM: 932agm
# PUBSPEC CHECKSUM: kav93d

View File

@ -147,7 +147,7 @@ name: my_app
}
testUsingContext(
'runner with web server device supports debugging without --start-paused',
'runner with web server device does not support debugging without --start-paused',
() {
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
flutterDevice.device = WebServerDevice(logger: BufferLogger.test());
@ -165,7 +165,7 @@ name: my_app
systemClock: globals.systemClock,
);
expect(profileResidentWebRunner.debuggingEnabled, true);
expect(profileResidentWebRunner.debuggingEnabled, false);
flutterDevice.device = chromeDevice;
@ -1104,12 +1104,7 @@ name: my_app
logger: logger,
systemClock: SystemClock.fixed(DateTime(2001)),
);
fakeVmServiceHost = FakeVmServiceHost(
requests: [
...kAttachExpectations,
const FakeVmServiceRequest(method: 'hotRestart'),
],
);
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations);
setupMocks();
flutterDevice.device = webServerDevice;
webDevFS.report = UpdateFSReport(success: true);
@ -1122,16 +1117,8 @@ name: my_app
expect(logger.statusText, contains('Restarted application in'));
expect(result.code, 0);
expect(
fakeAnalytics.sentEvents,
contains(
Event.timing(
workflow: 'hot',
variableName: 'web-incremental-restart',
elapsedMilliseconds: 0,
),
),
);
// web-server device does not send restart analytics
expect(fakeAnalytics.sentEvents, isEmpty);
},
overrides: <Type, Generator>{
Analytics: () => fakeAnalytics,
@ -1594,7 +1581,7 @@ name: my_app
'Sends unlaunched app.webLaunchUrl event for Web Server device',
() async {
final logger = BufferLogger.test();
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
setupMocks();
flutterDevice.device = WebServerDevice(logger: logger);
webDevFS.baseUri = Uri.parse('http://localhost:8765/app/');
@ -1678,9 +1665,7 @@ flutter:
mainLibName: 'my_app',
packages: <String, String>{'path_provider_linux': '../../path_provider_linux'},
);
final connectionInfoCompleter = Completer<DebugConnectionInfo>();
expect(await residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter), 0);
await connectionInfoCompleter.future;
expect(await residentWebRunner.run(), 0);
final File generatedLocalizationsFile = globals.fs
.directory('lib')
.childDirectory('l10n')
@ -2070,9 +2055,6 @@ class FakeWebDevFS extends Fake implements WebDevFS {
@override
PackageConfig? lastPackageConfig = PackageConfig.empty;
@override
var useDwdsWebSocketConnection = false;
@override
Future<Uri> create() async {
return baseUri;

View File

@ -1,193 +0,0 @@
// 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.
@Tags(<String>['flutter-test-driver'])
library;
import 'dart:async';
import 'dart:io' as io;
import 'package:file/file.dart';
import 'package:flutter_tools/src/web/chrome.dart';
import 'package:flutter_tools/src/web/web_device.dart' show WebServerDevice;
import '../src/common.dart';
import 'test_data/hot_reload_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
import 'transition_test_utils.dart';
void main() {
testAll();
}
void testAll({List<String> additionalCommandArgs = const <String>[]}) {
group('WebSocket DWDS connection'
'${additionalCommandArgs.isEmpty ? '' : ' with args: $additionalCommandArgs'}', () {
// Test configuration constants
const debugUrlTimeout = Duration(seconds: 20);
const appStartTimeout = Duration(seconds: 15);
const hotReloadTimeout = Duration(seconds: 10);
late Directory tempDir;
final project = HotReloadProject();
late FlutterRunTestDriver flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync('hot_reload_websocket_test.');
await project.setUpIn(tempDir);
flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
await flutter.stop();
tryToDelete(tempDir);
});
testWithoutContext(
'hot reload with headless Chrome WebSocket connection',
() async {
debugPrint('Starting WebSocket DWDS test with headless Chrome...');
// Set up listening for app output before starting
final stdout = StringBuffer();
final sawDebugUrl = Completer<String>();
final StreamSubscription<String> subscription = flutter.stdout.listen((String e) {
stdout.writeln(e);
// Extract the debug connection URL
if (e.contains('Waiting for connection from Dart debug extension at http://')) {
final debugUrlPattern = RegExp(
r'Waiting for connection from Dart debug extension at (http://[^\s]+)',
);
final Match? match = debugUrlPattern.firstMatch(e);
if (match != null && !sawDebugUrl.isCompleted) {
sawDebugUrl.complete(match.group(1)!);
}
}
});
io.Process? chromeProcess;
try {
// Step 1: Start Flutter app with web-server device (will wait for debug connection)
debugPrint('Step 1: Starting Flutter app with web-server device...');
// Start the app but don't wait for it to complete - it won't complete until Chrome connects
final Future<void> appStartFuture = runFlutterWithWebServerDevice(
flutter,
additionalCommandArgs: [...additionalCommandArgs, '--no-web-resources-cdn'],
);
// Step 2: Wait for DWDS debug URL to be available
debugPrint('Step 2: Waiting for DWDS debug service URL...');
final String debugUrl = await sawDebugUrl.future.timeout(
debugUrlTimeout,
onTimeout: () {
throw Exception('DWDS debug URL not found - app may not have started correctly');
},
);
debugPrint('✓ DWDS debug service available at: $debugUrl');
// Step 3: Launch headless Chrome to connect to DWDS
debugPrint('Step 3: Launching headless Chrome to connect to DWDS...');
chromeProcess = await _launchHeadlessChrome(debugUrl);
debugPrint('✓ Headless Chrome launched and connecting to DWDS');
// Step 4: Wait for app to start (Chrome connection established)
debugPrint('Step 4: Waiting for Flutter app to start after Chrome connection...');
await appStartFuture.timeout(
appStartTimeout,
onTimeout: () {
throw Exception('App startup did not complete after Chrome connection');
},
);
debugPrint('✓ Flutter app started successfully with WebSocket connection');
// Step 5: Test hot reload functionality
debugPrint('Step 5: Testing hot reload with WebSocket connection...');
await flutter.hotReload().timeout(
hotReloadTimeout,
onTimeout: () {
throw Exception('Hot reload timed out');
},
);
// Give some time for logs to capture
await Future<void>.delayed(const Duration(seconds: 2));
final output = stdout.toString();
expect(output, contains('Reloaded'), reason: 'Hot reload should complete successfully');
debugPrint('✓ Hot reload completed successfully with WebSocket connection');
// Verify the correct infrastructure was used
expect(
output,
contains('Waiting for connection from Dart debug extension'),
reason: 'Should wait for debug connection (WebSocket infrastructure)',
);
expect(output, contains('web-server'), reason: 'Should use web-server device');
debugPrint('✓ WebSocket DWDS test completed successfully');
debugPrint('✓ Verified: web-server device + DWDS + WebSocket connection + hot reload');
} finally {
await _cleanupResources(chromeProcess, subscription);
}
},
skip: !platform.isMacOS, // Skip on non-macOS platforms where Chrome paths may differ
);
});
}
/// Launches headless Chrome with the given debug URL.
/// Uses findChromeExecutable to locate Chrome on the current platform.
Future<io.Process> _launchHeadlessChrome(String debugUrl) async {
const chromeArgs = [
'--headless',
'--disable-gpu',
'--no-sandbox',
'--disable-extensions',
'--disable-dev-shm-usage',
'--remote-debugging-port=0',
];
final String chromePath = findChromeExecutable(platform, fileSystem);
try {
return await io.Process.start(chromePath, [...chromeArgs, debugUrl]);
} on Exception catch (e) {
throw Exception(
'Could not launch Chrome at $chromePath: $e. Please ensure Chrome is installed.',
);
}
}
/// Cleans up test resources (Chrome process and stdout subscription).
Future<void> _cleanupResources(
io.Process? chromeProcess,
StreamSubscription<String> subscription,
) async {
if (chromeProcess != null) {
try {
chromeProcess.kill();
await chromeProcess.exitCode;
debugPrint('Chrome process cleaned up');
} on Exception catch (e) {
debugPrint('Warning: Failed to clean up Chrome process: $e');
}
}
await subscription.cancel();
}
// Helper to run flutter with web-server device using WebSocket connection.
Future<void> runFlutterWithWebServerDevice(
FlutterRunTestDriver flutter, {
bool verbose = false,
bool withDebugger = true, // Enable debugger by default for WebSocket connection
bool startPaused = false, // Don't start paused for this test
List<String> additionalCommandArgs = const <String>[],
}) => flutter.run(
verbose: verbose,
withDebugger: withDebugger, // Enable debugger to establish WebSocket connection
startPaused: startPaused, // Let the app start normally after debugger connects
device: WebServerDevice.kWebServerDeviceId,
additionalCommandArgs: additionalCommandArgs,
);

View File

@ -11,7 +11,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/tester/flutter_tester.dart';
import 'package:flutter_tools/src/web/web_device.dart' show GoogleChromeDevice, WebServerDevice;
import 'package:flutter_tools/src/web/web_device.dart' show GoogleChromeDevice;
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'package:vm_service/vm_service.dart';
@ -568,7 +568,6 @@ final class FlutterRunTestDriver extends FlutterTestDriver {
],
withDebugger: withDebugger,
startPaused: startPaused,
waitForDebugPort: device != WebServerDevice.kWebServerDeviceId,
pauseOnExceptions: pauseOnExceptions,
script: script,
verbose: verbose,
@ -609,7 +608,6 @@ final class FlutterRunTestDriver extends FlutterTestDriver {
bool withDebugger = false,
bool startPaused = false,
bool pauseOnExceptions = false,
bool waitForDebugPort = false,
bool verbose = false,
int? attachPort,
}) async {
@ -650,11 +648,12 @@ final class FlutterRunTestDriver extends FlutterTestDriver {
event: 'app.started',
timeout: appStartTimeout,
);
late final Map<String, Object?> debugPort;
if (waitForDebugPort || withDebugger) {
debugPort = await _waitFor(event: 'app.debugPort', timeout: appStartTimeout);
}
if (withDebugger) {
final Map<String, Object?> debugPort = await _waitFor(
event: 'app.debugPort',
timeout: appStartTimeout,
);
final wsUriString = (debugPort['params']! as Map<String, Object?>)['wsUri']! as String;
_vmServiceWsUri = Uri.parse(wsUriString);
await connectToVmService(pauseOnExceptions: pauseOnExceptions);