[ Widget Previews ] Remove deprecated desktop support (#169703)

Also cleans up non-verbose logging output to be less noisy.
This commit is contained in:
Ben Konyi 2025-06-02 11:23:43 -04:00 committed by GitHub
parent d064c95c10
commit a63836aa2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 127 additions and 258 deletions

View File

@ -2,6 +2,8 @@
// 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:args/args.dart';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
@ -22,18 +24,17 @@ import '../convert.dart';
import '../dart/pub.dart';
import '../device.dart';
import '../flutter_manifest.dart';
import '../linux/build_linux.dart';
import '../macos/build_macos.dart';
import '../globals.dart' as globals;
import '../isolated/resident_web_runner.dart';
import '../project.dart';
import '../resident_runner.dart';
import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
import '../widget_preview/dtd_services.dart';
import '../widget_preview/preview_code_generator.dart';
import '../widget_preview/preview_detector.dart';
import '../widget_preview/preview_manifest.dart';
import '../windows/build_windows.dart';
import 'create_base.dart';
import 'daemon.dart';
class WidgetPreviewCommand extends FlutterCommand {
WidgetPreviewCommand({
@ -50,7 +51,7 @@ class WidgetPreviewCommand extends FlutterCommand {
}) {
addSubcommand(
WidgetPreviewStartCommand(
verboseHelp: verboseHelp,
verbose: verboseHelp,
logger: logger,
fs: fs,
projectFactory: projectFactory,
@ -118,7 +119,7 @@ abstract base class WidgetPreviewSubCommandBase extends FlutterCommand {
final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with CreateBase {
WidgetPreviewStartCommand({
this.verboseHelp = false,
this.verbose = false,
required this.logger,
required this.fs,
required this.projectFactory,
@ -136,18 +137,9 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
defaultsTo: true,
help: 'Launches the widget preview environment.',
// Should only be used for testing.
hide: !verboseHelp,
)
..addFlag(
kUseFlutterDesktop,
help: '(deprecated) Launches the widget preview environment using Flutter Desktop.',
hide: !verboseHelp,
)
..addFlag(
kHeadlessWeb,
help: 'Launches Chrome in headless mode for testing.',
hide: !verboseHelp,
hide: !verbose,
)
..addFlag(kHeadless, help: 'Launches Chrome in headless mode for testing.', hide: !verbose)
..addOption(
kWidgetPreviewScaffoldOutputDir,
help:
@ -158,8 +150,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
static const String kWidgetPreviewScaffoldName = 'widget_preview_scaffold';
static const String kLaunchPreviewer = 'launch-previewer';
static const String kUseFlutterDesktop = 'desktop';
static const String kHeadlessWeb = 'headless-web';
static const String kHeadless = 'headless';
static const String kWidgetPreviewScaffoldOutputDir = 'scaffold-output-dir';
/// Environment variable used to pass the DTD URI to the widget preview scaffold.
@ -177,9 +168,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
@override
String get name => 'start';
final bool verboseHelp;
bool get isWeb => !boolArg(kUseFlutterDesktop);
final bool verbose;
@override
final FileSystem fs;
@ -227,7 +216,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
);
/// The currently running instance of the widget preview scaffold.
AppInstance? _widgetPreviewApp;
ResidentRunner? _widgetPreviewApp;
@override
Future<FlutterCommandResult> runCommand() async {
@ -243,6 +232,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
customPreviewScaffoldOutput != null || _previewManifest.shouldGenerateProject();
// TODO(bkonyi): can this be moved?
widgetPreviewScaffold.createSync();
fs.currentDirectory = widgetPreviewScaffold;
if (generateScaffoldProject) {
// WARNING: this log message is used by test/integration.shard/widget_preview_test.dart
@ -258,27 +248,22 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
titleCaseProjectName: 'Widget Preview Scaffold',
flutterRoot: Cache.flutterRoot!,
dartSdkVersionBounds: '^${cache.dartSdkBuild}',
linux: platform.isLinux && !isWeb,
macos: platform.isMacOS && !isWeb,
windows: platform.isWindows && !isWeb,
web: isWeb,
web: true,
),
overwrite: true,
generateMetadata: false,
printStatusWhenWriting: verbose,
);
if (customPreviewScaffoldOutput != null) {
return FlutterCommandResult.success();
}
_previewManifest.generate();
// WARNING: this access of widgetPreviewScaffoldProject needs to happen
// after we generate the scaffold project as invoking the getter triggers
// lazy initialization of the preview scaffold's FlutterManifest before
// the scaffold project's pubspec has been generated.
// TODO(bkonyi): add logic to rebuild after SDK updates
await initialBuild(widgetPreviewScaffoldProject: rootProject.widgetPreviewScaffoldProject);
}
// WARNING: this access of widgetPreviewScaffoldProject needs to happen
// after we generate the scaffold project as invoking the getter triggers
// lazy initialization of the preview scaffold's FlutterManifest before
// the scaffold project's pubspec has been generated.
_previewCodeGenerator = PreviewCodeGenerator(
widgetPreviewScaffoldProject: rootProject.widgetPreviewScaffoldProject,
fs: fs,
@ -305,13 +290,13 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
if (boolArg(kLaunchPreviewer)) {
shutdownHooks.addShutdownHook(() async {
await _widgetPreviewApp?.stop();
await _widgetPreviewApp?.exitApp();
});
await configureDtd();
_widgetPreviewApp = await runPreviewEnvironment(
widgetPreviewScaffoldProject: rootProject.widgetPreviewScaffoldProject,
);
final int result = await _widgetPreviewApp!.runner.waitForAppToFinish();
final int result = await _widgetPreviewApp!.waitForAppToFinish();
if (result != 0) {
throwToolExit('Failed to launch the widget previewer.', exitCode: result);
}
@ -358,119 +343,13 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
}
}
/// Builds the application binary for the widget preview scaffold the first
/// time the widget preview command is run.
///
/// The resulting binary is used to speed up subsequent widget previewer launches
/// by acting as a basic scaffold to load previews into using hot reload / restart.
Future<void> initialBuild({required FlutterProject widgetPreviewScaffoldProject}) async {
// Generate initial package_config.json, otherwise the build will fail.
await pub.get(
context: PubContext.create,
project: widgetPreviewScaffoldProject,
offline: offline,
outputMode: PubOutputMode.summaryOnly,
);
// TODO(bkonyi): handle error case where desktop device isn't enabled.
await widgetPreviewScaffoldProject.ensureReadyForPlatformSpecificTooling(
releaseMode: false,
linuxPlatform: platform.isLinux && !isWeb,
macOSPlatform: platform.isMacOS && !isWeb,
windowsPlatform: platform.isWindows && !isWeb,
webPlatform: isWeb,
);
if (isWeb) {
return;
}
// WARNING: this log message is used by test/integration.shard/widget_preview_test.dart
logger.printStatus('Performing initial build of the Widget Preview Scaffold...');
final BuildInfo buildInfo = BuildInfo(
BuildMode.debug,
null,
treeShakeIcons: false,
packageConfigPath: widgetPreviewScaffoldProject.packageConfig.path,
);
if (platform.isMacOS) {
await buildMacOS(
flutterProject: widgetPreviewScaffoldProject,
buildInfo: buildInfo,
verboseLogging: false,
);
} else if (platform.isLinux) {
await buildLinux(
widgetPreviewScaffoldProject.linux,
buildInfo,
targetPlatform:
os.hostPlatform == HostPlatform.linux_x64
? TargetPlatform.linux_x64
: TargetPlatform.linux_arm64,
logger: logger,
);
} else if (platform.isWindows) {
await buildWindows(
widgetPreviewScaffoldProject.windows,
buildInfo,
os.hostPlatform == HostPlatform.windows_x64
? TargetPlatform.windows_x64
: TargetPlatform.windows_arm64,
);
} else {
throw UnimplementedError();
}
// WARNING: this log message is used by test/integration.shard/widget_preview_test.dart
logger.printStatus('Widget Preview Scaffold initial build complete.');
}
/// Returns the path to a prebuilt widget_preview_scaffold application binary.
String prebuiltApplicationBinaryPath({required FlutterProject widgetPreviewScaffoldProject}) {
assert(platform.isLinux || platform.isMacOS || platform.isWindows);
String path;
if (platform.isMacOS) {
path = fs.path.join(
getMacOSBuildDirectory(),
'Build/Products/Debug/widget_preview_scaffold.app',
);
} else if (platform.isLinux) {
path = fs.path.join(
getLinuxBuildDirectory(
os.hostPlatform == HostPlatform.linux_x64
? TargetPlatform.linux_x64
: TargetPlatform.linux_arm64,
),
'debug/bundle/widget_preview_scaffold',
);
} else if (platform.isWindows) {
path = fs.path.join(
getWindowsBuildDirectory(
os.hostPlatform == HostPlatform.windows_x64
? TargetPlatform.windows_x64
: TargetPlatform.windows_arm64,
),
'runner/Debug/widget_preview_scaffold.exe',
);
} else {
throw StateError('Unknown OS');
}
path = fs.path.join(widgetPreviewScaffoldProject.directory.path, path);
if (fs.typeSync(path) == FileSystemEntityType.notFound) {
logger.printStatus(fs.currentDirectory.toString());
throw StateError('Could not find prebuilt application binary at $path.');
}
return path;
}
Future<AppInstance> runPreviewEnvironment({
Future<ResidentRunner> runPreviewEnvironment({
required FlutterProject widgetPreviewScaffoldProject,
}) async {
final AppInstance app;
final ResidentWebRunner runner;
try {
// Since the only target supported by the widget preview scaffold is the host's desktop
// device, only a single desktop device should be returned.
// Since the only target supported by the widget preview scaffold is the web
// device, only a single web device should be returned.
final List<Device> devices = await deviceManager!.getDevices(
filter: DeviceDiscoveryFilter(
supportFilter: DeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutterOrProject(
@ -482,71 +361,63 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
assert(devices.length == 1);
final Device device = devices.first;
// We launch from a prebuilt widget preview scaffold instance to reduce launch times after
// the first run.
File? prebuiltApplicationBinary;
if (!isWeb) {
prebuiltApplicationBinary = fs.file(
prebuiltApplicationBinaryPath(widgetPreviewScaffoldProject: widgetPreviewScaffoldProject),
);
}
const String? kEmptyRoute = null;
const bool kEnableHotReload = true;
// WARNING: this log message is used by test/integration.shard/widget_preview_test.dart
logger.printStatus('Launching the Widget Preview Scaffold...');
app = await Daemon.createMachineDaemon().appDomain.startApp(
device,
widgetPreviewScaffoldProject.directory.path,
bundle.defaultMainPath,
kEmptyRoute, // route
DebuggingOptions.enabled(
BuildInfo(
BuildMode.debug,
null,
treeShakeIcons: false,
// Provide the DTD connection information directly to the preview scaffold.
// This could, in theory, be provided via a follow up call to a service extension
// registered by the preview scaffold, but there's some uncertainty around how service
// extensions will work with Flutter web embedded in VSCode without a Chrome debugger
// connection.
dartDefines: <String>['$kWidgetPreviewDtdUriEnvVar=${_dtdService.dtdUri}'],
extraFrontEndOptions:
isWeb ? <String>['--dartdevc-canary', '--dartdevc-module-format=ddc'] : null,
packageConfigPath: widgetPreviewScaffoldProject.packageConfig.path,
packageConfig: PackageConfig.parseBytes(
widgetPreviewScaffoldProject.packageConfig.readAsBytesSync(),
widgetPreviewScaffoldProject.packageConfig.uri,
),
// Don't try and download canvaskit from the CDN.
useLocalCanvasKit: true,
final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(
BuildInfo(
BuildMode.debug,
null,
treeShakeIcons: false,
// Provide the DTD connection information directly to the preview scaffold.
// This could, in theory, be provided via a follow up call to a service extension
// registered by the preview scaffold, but there's some uncertainty around how service
// extensions will work with Flutter web embedded in VSCode without a Chrome debugger
// connection.
dartDefines: <String>['$kWidgetPreviewDtdUriEnvVar=${_dtdService.dtdUri}'],
extraFrontEndOptions: <String>['--dartdevc-canary', '--dartdevc-module-format=ddc'],
packageConfigPath: widgetPreviewScaffoldProject.packageConfig.path,
packageConfig: PackageConfig.parseBytes(
widgetPreviewScaffoldProject.packageConfig.readAsBytesSync(),
widgetPreviewScaffoldProject.packageConfig.uri,
),
webEnableExposeUrl: false,
webRunHeadless: boolArg(kHeadlessWeb),
// Don't try and download canvaskit from the CDN.
useLocalCanvasKit: true,
),
kEnableHotReload, // hot mode
applicationBinary: prebuiltApplicationBinary,
trackWidgetCreation: true,
projectRootPath: widgetPreviewScaffoldProject.directory.path,
webEnableExposeUrl: false,
webRunHeadless: boolArg(kHeadless),
);
app.runner.residentDevtoolsHandler!.launchDevToolsInBrowser(
flutterDevices: app.runner.flutterDevices,
final String target = bundle.defaultMainPath;
final FlutterDevice flutterDevice = await FlutterDevice.create(
device,
target: target,
buildInfo: debuggingOptions.buildInfo,
platform: platform,
);
runner = ResidentWebRunner(
flutterDevice,
target: target,
debuggingOptions: debuggingOptions,
analytics: analytics,
flutterProject: widgetPreviewScaffoldProject,
fileSystem: fs,
logger: logger,
terminal: globals.terminal,
platform: platform,
outputPreferences: globals.outputPreferences,
systemClock: globals.systemClock,
);
final Completer<void> appStarted = Completer<void>();
unawaited(runner.run(appStartedCompleter: appStarted));
await appStarted.future;
} on Exception catch (error) {
throwToolExit(error.toString());
}
if (!isWeb) {
// Immediately perform a hot restart to ensure new previews are loaded into the prebuilt
// application.
// WARNING: this log message is used by test/integration.shard/widget_preview_test.dart
logger.printStatus('Loading previews into the Widget Preview Scaffold...');
await app.restart(fullRestart: true);
}
// WARNING: this log message is used by test/integration.shard/widget_preview_test.dart
logger.printStatus('Done loading previews.');
return app;
return runner;
}
@visibleForTesting
@ -643,6 +514,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
'path': rootProject.directory.fileSystem.path.relative(rootProject.directory.path),
});
final PubOutputMode outputMode = verbose ? PubOutputMode.all : PubOutputMode.failuresOnly;
await pub.interactively(
<String>[
pubAdd,
@ -655,6 +527,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
context: PubContext.pubAdd,
command: pubAdd,
touchesPackageConfig: true,
outputMode: outputMode,
);
// Adds dependencies on:
@ -679,6 +552,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
context: PubContext.pubAdd,
command: pubAdd,
touchesPackageConfig: true,
outputMode: outputMode,
);
// Generate package_config.json.
@ -686,7 +560,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
context: PubContext.create,
project: widgetPreviewScaffoldProject,
offline: offline,
outputMode: PubOutputMode.summaryOnly,
outputMode: outputMode,
);
maybeAddFlutterGenToPackageConfig(rootProject: rootProject);

View File

@ -38,6 +38,28 @@ class LoggingProcessManager extends LocalProcessManager {
);
}
@override
Future<ProcessResult> run(
List<Object> command, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
Encoding? stdoutEncoding = systemEncoding,
Encoding? stderrEncoding = systemEncoding,
}) {
commands.add(command.map((Object arg) => arg.toString()).toList());
return super.run(
command,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
stdoutEncoding: stdoutEncoding,
stderrEncoding: stderrEncoding,
);
}
void clear() {
commands.clear();
}

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' as io show IOOverrides;
import 'package:args/command_runner.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
@ -18,6 +16,7 @@ import 'package:flutter_tools/src/base/signals.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/widget_preview.dart';
import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/widget_preview/preview_code_generator.dart';
@ -28,6 +27,7 @@ import '../../src/test_flutter_command_runner.dart';
import 'utils/project_testing_utils.dart';
void main() {
late Directory originalCwd;
late Directory tempDir;
late LoggingProcessManager loggingProcessManager;
late FakeStdio mockStdio;
@ -37,6 +37,7 @@ void main() {
late Platform platform;
setUp(() async {
originalCwd = globals.fs.currentDirectory;
await ensureFlutterToolsSnapshot();
loggingProcessManager = LoggingProcessManager();
logger = BufferLogger.test();
@ -55,6 +56,7 @@ void main() {
tearDown(() {
tryToDelete(tempDir);
fs.dispose();
globals.fs.currentDirectory = originalCwd;
});
Future<Directory> createRootProject() async {
@ -92,14 +94,17 @@ void main() {
required Directory? rootProject,
List<String>? arguments,
}) async {
// This might get changed during the test, so keep track of the original directory.
final Directory current = fs.currentDirectory;
await runWidgetPreviewCommand(<String>[
'start',
...?arguments,
'--no-launch-previewer',
'--verbose',
if (rootProject != null) rootProject.path,
]);
final Directory widgetPreviewScaffoldDir = widgetPreviewScaffoldFromRootProject(
rootProject: rootProject ?? fs.currentDirectory,
rootProject: rootProject ?? current,
);
// Don't perform analysis on Windows since `dart pub add` will use '\' for
// path dependencies and cause analysis to fail.
@ -108,6 +113,7 @@ void main() {
if (!platform.isWindows) {
await analyzeProject(widgetPreviewScaffoldDir.path);
}
fs.currentDirectory = current;
}
Future<void> cleanWidgetPreview({required Directory rootProject}) async {
@ -158,6 +164,8 @@ void main() {
await startWidgetPreview(rootProject: rootProject);
},
overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => loggingProcessManager,
Pub:
() => Pub.test(
fileSystem: fs,
@ -174,12 +182,13 @@ void main() {
'start creates .dart_tool/widget_preview_scaffold in the CWD',
() async {
final Directory rootProject = await createRootProject();
await io.IOOverrides.runZoned<Future<void>>(() async {
// Try to execute using the CWD.
await startWidgetPreview(rootProject: null);
}, getCurrentDirectory: () => rootProject);
// Try to execute using the CWD.
fs.currentDirectory = rootProject;
await startWidgetPreview(rootProject: null);
},
overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => loggingProcessManager,
Pub:
() => Pub.test(
fileSystem: fs,
@ -257,13 +266,16 @@ List<_i1.WidgetPreview> previews() => [
PreviewCodeGenerator.generatedPreviewFilePath,
);
await io.IOOverrides.runZoned<Future<void>>(() async {
// Try to execute using the CWD.
await startWidgetPreview(rootProject: null);
expect(generatedFile.readAsStringSync(), expectedGeneratedFileContents);
}, getCurrentDirectory: () => fs.directory(rootProject));
// Try to execute using the CWD.
fs.currentDirectory = rootProject;
await startWidgetPreview(rootProject: null);
expect(generatedFile.readAsStringSync(), expectedGeneratedFileContents);
},
overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => loggingProcessManager,
Pub:
() => Pub.test(
fileSystem: fs,

View File

@ -20,21 +20,6 @@ import '../src/context.dart';
import 'test_data/basic_project.dart';
import 'test_utils.dart';
const List<String> firstLaunchMessages = <String>[
'Creating widget preview scaffolding at:',
'Performing initial build of the Widget Preview Scaffold...',
'Widget Preview Scaffold initial build complete.',
'Launching the Widget Preview Scaffold...',
'Loading previews into the Widget Preview Scaffold...',
'Done loading previews.',
];
const List<String> subsequentLaunchMessages = <String>[
'Launching the Widget Preview Scaffold...',
'Loading previews into the Widget Preview Scaffold...',
'Done loading previews.',
];
const List<String> firstLaunchMessagesWeb = <String>[
'Creating widget preview scaffolding at:',
'Launching the Widget Preview Scaffold...',
@ -68,11 +53,7 @@ void main() {
tryToDelete(tempDir);
});
Future<void> runWidgetPreview({
required List<String> expectedMessages,
bool useWeb = false,
Uri? dtdUri,
}) async {
Future<void> runWidgetPreview({required List<String> expectedMessages, Uri? dtdUri}) async {
expect(expectedMessages, isNotEmpty);
int i = 0;
process = await processManager.start(<String>[
@ -80,10 +61,7 @@ void main() {
'widget-preview',
'start',
'--verbose',
if (useWeb)
'--${WidgetPreviewStartCommand.kHeadlessWeb}'
else
'--${WidgetPreviewStartCommand.kUseFlutterDesktop}',
'--${WidgetPreviewStartCommand.kHeadless}',
if (dtdUri != null) '--${FlutterGlobalOptions.kDtdUrl}=$dtdUri',
], workingDirectory: tempDir.path);
@ -120,28 +98,15 @@ void main() {
group('flutter widget-preview start', () {
testWithoutContext('smoke test', () async {
await runWidgetPreview(expectedMessages: firstLaunchMessages);
await runWidgetPreview(expectedMessages: firstLaunchMessagesWeb);
});
testWithoutContext('web smoke test', () async {
await runWidgetPreview(expectedMessages: firstLaunchMessagesWeb, useWeb: true);
});
testWithoutContext('does not rebuild project on subsequent runs', () async {
// The first run of 'flutter widget-preview start' should generate a new preview scaffold and
// pre-build the application.
await runWidgetPreview(expectedMessages: firstLaunchMessages);
testWithoutContext('does not recreate project on subsequent runs', () async {
// The first run of 'flutter widget-preview start' should generate a new preview scaffold
await runWidgetPreview(expectedMessages: firstLaunchMessagesWeb);
// We shouldn't regenerate the scaffold after the initial run.
await runWidgetPreview(expectedMessages: subsequentLaunchMessages);
});
testWithoutContext('does not recreate project on subsequent --web runs', () async {
// The first run of 'flutter widget-preview start --web' should generate a new preview scaffold
await runWidgetPreview(expectedMessages: firstLaunchMessagesWeb, useWeb: true);
// We shouldn't regenerate the scaffold after the initial run.
await runWidgetPreview(expectedMessages: subsequentLaunchMessagesWeb, useWeb: true);
await runWidgetPreview(expectedMessages: subsequentLaunchMessagesWeb);
});
testUsingContext('can connect to an existing DTD instance', () async {
@ -169,11 +134,7 @@ void main() {
await dtdConnection.streamListen(kWidgetPreviewScaffoldStream);
// Start the widget preview and wait for the 'Connected' event.
await runWidgetPreview(
expectedMessages: firstLaunchMessagesWeb,
useWeb: true,
dtdUri: dtdUri,
);
await runWidgetPreview(expectedMessages: firstLaunchMessagesWeb, dtdUri: dtdUri);
await completer.future;
});
});