Migrate package:test except for the node platform (#1444)

Enables many more sound null safe tests, and the only runner code opted out is the node platform and executable itself.
diff --git a/pkgs/test/CHANGELOG.md b/pkgs/test/CHANGELOG.md
index fade26f..6e6385d 100644
--- a/pkgs/test/CHANGELOG.md
+++ b/pkgs/test/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 1.16.6-dev
 
+* Complete the migration of all but the node platform to null safety.
+
 ## 1.16.5
 
 * Expand several deps to allow the latest versions.
diff --git a/pkgs/test/lib/src/runner/browser/browser.dart b/pkgs/test/lib/src/runner/browser/browser.dart
index 87314db..8cd8278 100644
--- a/pkgs/test/lib/src/runner/browser/browser.dart
+++ b/pkgs/test/lib/src/runner/browser/browser.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'dart:convert';
@@ -26,15 +24,15 @@
 
   /// The Observatory URL for this browser.
   ///
-  /// This will return `null` for browsers that aren't running the Dart VM, or
-  /// if the Observatory URL can't be found.
-  Future<Uri> get observatoryUrl => null;
+  /// This will complete to `null` for browsers that aren't running the Dart VM,
+  /// or if the Observatory URL can't be found.
+  Future<Uri?> get observatoryUrl async => null;
 
   /// The remote debugger URL for this browser.
   ///
-  /// This will return `null` for browsers that don't support remote debugging,
-  /// or if the remote debugging URL can't be found.
-  Future<Uri> get remoteDebuggerUrl => null;
+  /// This will  complete to `null` for browsers that don't support remote
+  /// debugging, or if the remote debugging URL can't be found.
+  Future<Uri?> get remoteDebuggerUrl async => null;
 
   /// The underlying process.
   ///
@@ -140,6 +138,6 @@
     (await _process).kill();
 
     // Swallow exceptions. The user should explicitly use [onExit] for these.
-    return onExit.catchError((_) {});
+    return onExit.onError((_, __) {});
   }
 }
diff --git a/pkgs/test/lib/src/runner/browser/browser_manager.dart b/pkgs/test/lib/src/runner/browser/browser_manager.dart
index d966c9d..3a06c5c 100644
--- a/pkgs/test/lib/src/runner/browser/browser_manager.dart
+++ b/pkgs/test/lib/src/runner/browser/browser_manager.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'dart:convert';
@@ -45,7 +43,7 @@
   /// The channel used to communicate with the browser.
   ///
   /// This is connected to a page running `static/host.dart`.
-  MultiChannel<Object> _channel;
+  late final MultiChannel<Object> _channel;
 
   /// A pool that ensures that limits the number of initial connections the
   /// manager will wait for at once.
@@ -69,13 +67,13 @@
   ///
   /// This will be `null` as long as the browser isn't displaying a pause
   /// screen.
-  CancelableCompleter<void> _pauseCompleter;
+  CancelableCompleter<void>? _pauseCompleter;
 
   /// The controller for [_BrowserEnvironment.onRestart].
   final _onRestartController = StreamController<Null>.broadcast();
 
   /// The environment to attach to each suite.
-  Future<_BrowserEnvironment> _environment;
+  late final Future<_BrowserEnvironment> _environment;
 
   /// Controllers for every suite in this browser.
   ///
@@ -87,7 +85,7 @@
   //
   // Because the browser stops running code when the user is actively debugging,
   // this lets us detect whether they're debugging reasonably accurately.
-  RestartableTimer _timer;
+  late final RestartableTimer _timer;
 
   /// Starts the browser identified by [runtime] and has it connect to [url].
   ///
@@ -112,17 +110,18 @@
 
     // TODO(nweiz): Gracefully handle the browser being killed before the
     // tests complete.
-    browser.onExit.then((_) {
+    browser.onExit.then<void>((_) {
       throw ApplicationException('${runtime.name} exited before connecting.');
-    }).catchError((error, StackTrace stackTrace) {
-      if (completer.isCompleted) return;
-      completer.completeError(error, stackTrace);
+    }).onError<Object>((error, stackTrace) {
+      if (!completer.isCompleted) {
+        completer.completeError(error, stackTrace);
+      }
     });
 
     future.then((webSocket) {
       if (completer.isCompleted) return;
       completer.complete(BrowserManager._(browser, runtime, webSocket));
-    }).catchError((error, StackTrace stackTrace) {
+    }).onError((Object error, StackTrace stackTrace) {
       browser.close();
       if (completer.isCompleted) return;
       completer.completeError(error, stackTrace);
@@ -187,8 +186,9 @@
     }));
 
     _environment = _loadBrowserEnvironment();
-    _channel.stream
-        .listen((message) => _onMessage(message as Map), onDone: close);
+    _channel.stream.listen(
+        (message) => _onMessage(message as Map<Object, Object>),
+        onDone: close);
   }
 
   /// Loads [_BrowserEnvironment].
@@ -207,7 +207,7 @@
   /// from this test suite.
   Future<RunnerSuite> load(
       String path, Uri url, SuiteConfiguration suiteConfig, Object message,
-      {StackTraceMapper mapper}) async {
+      {StackTraceMapper? mapper}) async {
     url = url.replace(
         fragment: Uri.encodeFull(jsonEncode({
       'metadata': suiteConfig.metadata.serialize(),
@@ -215,10 +215,10 @@
     })));
 
     var suiteID = _suiteID++;
-    RunnerSuiteController controller;
+    RunnerSuiteController? controller;
     void closeIframe() {
       if (_closed) return;
-      _controllers.remove(controller);
+      if (controller != null) _controllers.remove(controller);
       _channel.sink.add({'command': 'closeSuite', 'id': suiteID});
     }
 
@@ -246,17 +246,20 @@
             currentPlatform(_runtime),
             suiteConfig,
             await _environment,
-            suiteChannel,
+            suiteChannel.cast(),
             message, gatherCoverage: () async {
           var browser = _browser;
           if (browser is Chrome) return browser.gatherCoverage();
           return {};
         });
 
-        controller.channel('test.browser.mapper').sink.add(mapper?.serialize());
+        controller!
+            .channel('test.browser.mapper')
+            .sink
+            .add(mapper?.serialize());
 
-        _controllers.add(controller);
-        return await controller.suite;
+        _controllers.add(controller!);
+        return await controller!.suite;
       } catch (_) {
         closeIframe();
         rethrow;
@@ -266,20 +269,20 @@
 
   /// An implementation of [Environment.displayPause].
   CancelableOperation<void> _displayPause() {
-    if (_pauseCompleter != null) return _pauseCompleter.operation;
+    if (_pauseCompleter != null) return _pauseCompleter!.operation;
 
-    _pauseCompleter = CancelableCompleter(onCancel: () {
+    final pauseCompleter = _pauseCompleter = CancelableCompleter(onCancel: () {
       _channel.sink.add({'command': 'resume'});
       _pauseCompleter = null;
     });
 
-    _pauseCompleter.operation.value.whenComplete(() {
+    pauseCompleter.operation.value.whenComplete(() {
       _pauseCompleter = null;
     });
 
     _channel.sink.add({'command': 'displayPause'});
 
-    return _pauseCompleter.operation;
+    return pauseCompleter.operation;
   }
 
   /// The callback for handling messages received from the host page.
@@ -293,7 +296,7 @@
         break;
 
       case 'resume':
-        if (_pauseCompleter != null) _pauseCompleter.complete();
+        _pauseCompleter?.complete();
         break;
 
       default:
@@ -308,7 +311,7 @@
   Future<void> close() => _closeMemoizer.runOnce(() {
         _closed = true;
         _timer.cancel();
-        if (_pauseCompleter != null) _pauseCompleter.complete();
+        _pauseCompleter?.complete();
         _pauseCompleter = null;
         _controllers.clear();
         return _browser.close();
@@ -326,10 +329,10 @@
   final supportsDebugging = true;
 
   @override
-  final Uri observatoryUrl;
+  final Uri? observatoryUrl;
 
   @override
-  final Uri remoteDebuggerUrl;
+  final Uri? remoteDebuggerUrl;
 
   @override
   final Stream<Null> onRestart;
diff --git a/pkgs/test/lib/src/runner/browser/chrome.dart b/pkgs/test/lib/src/runner/browser/chrome.dart
index 19cd5e6..ea2fe02 100644
--- a/pkgs/test/lib/src/runner/browser/chrome.dart
+++ b/pkgs/test/lib/src/runner/browser/chrome.dart
@@ -1,13 +1,12 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:collection/collection.dart';
 import 'package:coverage/coverage.dart';
 import 'package:path/path.dart' as p;
 import 'package:pedantic/pedantic.dart';
@@ -32,7 +31,7 @@
   final name = 'Chrome';
 
   @override
-  final Future<Uri> remoteDebuggerUrl;
+  final Future<Uri?> remoteDebuggerUrl;
 
   final Future<WipConnection> _tabConnection;
   final Map<String, String> _idToUrl;
@@ -40,13 +39,13 @@
   /// Starts a new instance of Chrome open to the given [url], which may be a
   /// [Uri] or a [String].
   factory Chrome(Uri url, Configuration configuration,
-      {ExecutableSettings settings}) {
-    settings ??= defaultSettings[Runtime.chrome];
-    var remoteDebuggerCompleter = Completer<Uri>.sync();
+      {ExecutableSettings? settings}) {
+    settings ??= defaultSettings[Runtime.chrome]!;
+    var remoteDebuggerCompleter = Completer<Uri?>.sync();
     var connectionCompleter = Completer<WipConnection>();
     var idToUrl = <String, String>{};
     return Chrome._(() async {
-      var tryPort = ([int port]) async {
+      var tryPort = ([int? port]) async {
         var dir = createTempDir();
         var args = [
           '--user-data-dir=$dir',
@@ -59,7 +58,7 @@
           '--disable-default-apps',
           '--disable-translate',
           '--disable-dev-shm-usage',
-          if (settings.headless && !configuration.pauseAfterLoad) ...[
+          if (settings!.headless && !configuration.pauseAfterLoad) ...[
             '--headless',
             '--disable-gpu',
           ],
@@ -105,10 +104,11 @@
     var tabConnection = await _tabConnection;
     var response = await tabConnection.debugger.connection
         .sendCommand('Profiler.takePreciseCoverage', {});
-    var result = response.result['result'];
+    var result =
+        (response.result!['result'] as List).cast<Map<String, dynamic>>();
     var httpClient = HttpClient();
     var coverage = await parseChromeCoverage(
-      (result as List).cast(),
+      result,
       (scriptId) => _sourceProvider(scriptId, httpClient),
       (scriptId) => _sourceMapProvider(scriptId, httpClient),
       _sourceUriProvider,
@@ -121,7 +121,7 @@
       this._tabConnection, this._idToUrl)
       : super(startBrowser);
 
-  Future<Uri> _sourceUriProvider(String sourceUrl, String scriptId) async {
+  Future<Uri?> _sourceUriProvider(String sourceUrl, String scriptId) async {
     var script = _idToUrl[scriptId];
     if (script == null) return null;
     var sourceUri = Uri.parse(sourceUrl);
@@ -136,14 +136,15 @@
         : null;
   }
 
-  Future<String> _sourceMapProvider(
+  Future<String?> _sourceMapProvider(
       String scriptId, HttpClient httpClient) async {
     var script = _idToUrl[scriptId];
     if (script == null) return null;
     return await httpClient.getString('$script.map');
   }
 
-  Future<String> _sourceProvider(String scriptId, HttpClient httpClient) async {
+  Future<String?> _sourceProvider(
+      String scriptId, HttpClient httpClient) async {
     var script = _idToUrl[scriptId];
     if (script == null) return null;
     return await httpClient.getString(script);
@@ -159,13 +160,12 @@
       .firstWhere((line) => line.startsWith('DevTools listening'));
 
   var chromeConnection = ChromeConnection('localhost', port);
-  ChromeTab tab;
+  ChromeTab? tab;
   var attempt = 0;
   while (tab == null) {
     attempt++;
     var tabs = await chromeConnection.getTabs();
-    tab =
-        tabs.firstWhere((tab) => tab.url == url.toString(), orElse: () => null);
+    tab = tabs.firstWhereOrNull((tab) => tab.url == url.toString());
     if (tab == null) {
       await Future.delayed(Duration(milliseconds: 100));
       if (attempt > 5) {
@@ -193,7 +193,7 @@
 }
 
 extension on HttpClient {
-  Future<String> getString(String url) async {
+  Future<String?> getString(String url) async {
     final request = await getUrl(Uri.parse(url));
     final response = await request.close();
     if (response.statusCode != HttpStatus.ok) return null;
diff --git a/pkgs/test/lib/src/runner/browser/default_settings.dart b/pkgs/test/lib/src/runner/browser/default_settings.dart
index 7818713..9ff1fd4 100644
--- a/pkgs/test/lib/src/runner/browser/default_settings.dart
+++ b/pkgs/test/lib/src/runner/browser/default_settings.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:collection';
 
diff --git a/pkgs/test/lib/src/runner/browser/firefox.dart b/pkgs/test/lib/src/runner/browser/firefox.dart
index caabe1f..d0ab1b6 100644
--- a/pkgs/test/lib/src/runner/browser/firefox.dart
+++ b/pkgs/test/lib/src/runner/browser/firefox.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'dart:io';
@@ -33,13 +31,13 @@
   @override
   final name = 'Firefox';
 
-  Firefox(url, {ExecutableSettings settings})
-      : super(() => _startBrowser(url, settings));
+  Firefox(url, {ExecutableSettings? settings})
+      : super(() =>
+            _startBrowser(url, settings ?? defaultSettings[Runtime.firefox]!));
 
   /// Starts a new instance of Firefox open to the given [url], which may be a
   /// [Uri] or a [String].
   static Future<Process> _startBrowser(url, ExecutableSettings settings) async {
-    settings ??= defaultSettings[Runtime.firefox];
     var dir = createTempDir();
     File(p.join(dir, 'prefs.js')).writeAsStringSync(_preferences);
 
diff --git a/pkgs/test/lib/src/runner/browser/internet_explorer.dart b/pkgs/test/lib/src/runner/browser/internet_explorer.dart
index c191adc..2ffe0b0 100644
--- a/pkgs/test/lib/src/runner/browser/internet_explorer.dart
+++ b/pkgs/test/lib/src/runner/browser/internet_explorer.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'dart:io';
@@ -19,14 +17,13 @@
   @override
   final name = 'Internet Explorer';
 
-  InternetExplorer(url, {ExecutableSettings settings})
-      : super(() => _startBrowser(url, settings));
+  InternetExplorer(url, {ExecutableSettings? settings})
+      : super(() => _startBrowser(
+            url, settings ?? defaultSettings[Runtime.internetExplorer]!));
 
   /// Starts a new instance of Internet Explorer open to the given [url], which
   /// may be a [Uri] or a [String].
   static Future<Process> _startBrowser(url, ExecutableSettings settings) {
-    settings ??= defaultSettings[Runtime.internetExplorer];
-
     return Process.start(settings.executable, [
       '-extoff',
       '$url',
diff --git a/pkgs/test/lib/src/runner/browser/phantom_js.dart b/pkgs/test/lib/src/runner/browser/phantom_js.dart
index ed49a2a..bdcbb91 100644
--- a/pkgs/test/lib/src/runner/browser/phantom_js.dart
+++ b/pkgs/test/lib/src/runner/browser/phantom_js.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'dart:io';
@@ -50,8 +48,8 @@
   final Future<Uri> remoteDebuggerUrl;
 
   factory PhantomJS(url, Configuration configuration,
-      {ExecutableSettings settings}) {
-    settings ??= defaultSettings[Runtime.phantomJS];
+      {ExecutableSettings? settings}) {
+    settings ??= defaultSettings[Runtime.phantomJS]!;
     var remoteDebuggerCompleter = Completer<Uri>.sync();
     return PhantomJS._(() async {
       var dir = createTempDir();
@@ -60,7 +58,7 @@
 
       var port = configuration.debug ? await getUnsafeUnusedPort() : null;
 
-      var args = settings.arguments.toList();
+      var args = settings!.arguments.toList();
       if (configuration.debug) {
         args.addAll(
             ['--remote-debugger-port=$port', '--remote-debugger-autorun=yes']);
diff --git a/pkgs/test/lib/src/runner/browser/platform.dart b/pkgs/test/lib/src/runner/browser/platform.dart
index 8e4c2c8..8a4b7f8 100644
--- a/pkgs/test/lib/src/runner/browser/platform.dart
+++ b/pkgs/test/lib/src/runner/browser/platform.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'dart:convert';
@@ -49,7 +47,7 @@
   ///
   /// [root] is the root directory that the server should serve. It defaults to
   /// the working directory.
-  static Future<BrowserPlatform> start({String root}) async {
+  static Future<BrowserPlatform> start({String? root}) async {
     var server = shelf_io.IOServer(await HttpMultiServer.loopback(0));
     var packageConfig = await currentPackageConfig;
     return BrowserPlatform._(
@@ -91,7 +89,7 @@
   final _compilers = CompilerPool();
 
   /// The temporary directory in which compiled JS is emitted.
-  final String _compiledDir;
+  final String? _compiledDir;
 
   /// The root directory served statically by this server.
   final String _root;
@@ -103,7 +101,7 @@
   final _pubServePool = Pool(1);
 
   /// The HTTP client to use when caching JS files in `pub serve`.
-  final HttpClient _http;
+  final HttpClient? _http;
 
   /// Whether [close] has been called.
   bool get _closed => _closeMemo.hasRun;
@@ -112,7 +110,7 @@
   /// [BrowserManager]s for those browsers, or `null` if they failed to load.
   ///
   /// This should only be accessed through [_browserManagerFor].
-  final _browserManagers = <Runtime, Future<BrowserManager>>{};
+  final _browserManagers = <Runtime, Future<BrowserManager?>>{};
 
   /// Settings for invoking each browser.
   ///
@@ -135,7 +133,7 @@
 
   BrowserPlatform._(this._server, Configuration config, String faviconPath,
       this._defaultTemplatePath,
-      {String root})
+      {String? root})
       : _config = config,
         _root = root ?? p.current,
         _compiledDir = config.pubServeUrl == null ? createTempDir() : null,
@@ -208,7 +206,7 @@
   /// This will start a browser to load the suite if one isn't already running.
   /// Throws an [ArgumentError] if `platform.platform` isn't a browser.
   @override
-  Future<RunnerSuite> load(String path, SuitePlatform platform,
+  Future<RunnerSuite?> load(String path, SuitePlatform platform,
       SuiteConfiguration suiteConfig, Object message) async {
     var browser = platform.runtime;
     assert(suiteConfig.runtimes.contains(browser.identifier));
@@ -221,15 +219,15 @@
     if (File(htmlPathFromTestPath).existsSync()) {
       if (_config.customHtmlTemplatePath != null &&
           p.basename(htmlPathFromTestPath) ==
-              p.basename(_config.customHtmlTemplatePath)) {
+              p.basename(_config.customHtmlTemplatePath!)) {
         throw LoadException(
             path,
-            'template file "${p.basename(_config.customHtmlTemplatePath)}" cannot be named '
+            'template file "${p.basename(_config.customHtmlTemplatePath!)}" cannot be named '
             'like the test file.');
       }
       _checkHtmlCorrectness(htmlPathFromTestPath, path);
     } else if (_config.customHtmlTemplatePath != null) {
-      var htmlTemplatePath = _config.customHtmlTemplatePath;
+      var htmlTemplatePath = _config.customHtmlTemplatePath!;
       if (!File(htmlTemplatePath).existsSync()) {
         throw LoadException(
             path, '"${htmlTemplatePath}" does not exist or is not readable');
@@ -251,10 +249,10 @@
           .path;
 
       var dartUrl =
-          _config.pubServeUrl.resolve('$suitePrefix.dart.browser_test.dart');
+          _config.pubServeUrl!.resolve('$suitePrefix.dart.browser_test.dart');
 
       await _pubServeSuite(path, dartUrl, browser, suiteConfig);
-      suiteUrl = _config.pubServeUrl.resolveUri(p.toUri('$suitePrefix.html'));
+      suiteUrl = _config.pubServeUrl!.resolveUri(p.toUri('$suitePrefix.html'));
     } else {
       if (browser.isJS) {
         if (suiteConfig.precompiledPath == null) {
@@ -317,7 +315,7 @@
 
       HttpClientResponse response;
       try {
-        var request = await _http.getUrl(url);
+        var request = await _http!.getUrl(url);
         response = await request.close();
 
         if (response.statusCode != 200) {
@@ -346,8 +344,8 @@
       } on IOException catch (error) {
         var message = getErrorMessage(error);
         if (error is SocketException) {
-          message = '${error.osError.message} '
-              '(errno ${error.osError.errorCode})';
+          message = '${error.osError?.message} '
+              '(errno ${error.osError?.errorCode})';
         }
 
         throw LoadException(
@@ -366,7 +364,7 @@
   /// served.
   Future<void> _compileSuite(String dartPath, SuiteConfiguration suiteConfig) {
     return _compileFutures.putIfAbsent(dartPath, () async {
-      var dir = Directory(_compiledDir).createTempSync('test_').path;
+      var dir = Directory(_compiledDir!).createTempSync('test_').path;
       var jsPath = p.join(dir, p.basename(dartPath) + '.browser_test.dart.js');
       var bootstrapContent = '''
         ${suiteConfig.metadata.languageVersionComment ?? await rootPackageLanguageVersionComment}
@@ -416,7 +414,7 @@
       String dartPath, SuiteConfiguration suiteConfig) async {
     if (suiteConfig.jsTrace) return;
     var mapPath = p.join(
-        suiteConfig.precompiledPath, dartPath + '.browser_test.dart.js.map');
+        suiteConfig.precompiledPath!, dartPath + '.browser_test.dart.js.map');
     var mapFile = File(mapPath);
     if (mapFile.existsSync()) {
       _mappers[dartPath] = JSStackTraceMapper(mapFile.readAsStringSync(),
@@ -429,7 +427,7 @@
   /// Returns the [BrowserManager] for [runtime], which should be a browser.
   ///
   /// If no browser manager is running yet, starts one.
-  Future<BrowserManager> _browserManagerFor(Runtime browser) {
+  Future<BrowserManager?> _browserManagerFor(Runtime browser) {
     var managerFuture = _browserManagers[browser];
     if (managerFuture != null) return managerFuture;
 
@@ -443,12 +441,13 @@
       'debug': _config.debug.toString()
     });
 
-    var future = BrowserManager.start(
-        browser, hostUrl, completer.future, _browserSettings[browser], _config);
+    var future = BrowserManager.start(browser, hostUrl, completer.future,
+        _browserSettings[browser]!, _config);
 
     // Store null values for browsers that error out so we know not to load them
     // again.
-    _browserManagers[browser] = future.catchError((_) => null);
+    _browserManagers[browser] =
+        future.then<BrowserManager?>((value) => value).onError((_, __) => null);
 
     return future;
   }
@@ -488,9 +487,9 @@
         await Future.wait(futures);
 
         if (_config.pubServeUrl == null) {
-          Directory(_compiledDir).deleteSync(recursive: true);
+          Directory(_compiledDir!).deleteSync(recursive: true);
         } else {
-          _http.close();
+          _http!.close();
         }
       });
   final _closeMemo = AsyncMemoizer<void>();
diff --git a/pkgs/test/lib/src/runner/browser/safari.dart b/pkgs/test/lib/src/runner/browser/safari.dart
index a8eb8f0..a549372 100644
--- a/pkgs/test/lib/src/runner/browser/safari.dart
+++ b/pkgs/test/lib/src/runner/browser/safari.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'dart:convert';
@@ -24,13 +22,13 @@
   @override
   final name = 'Safari';
 
-  Safari(url, {ExecutableSettings settings})
-      : super(() => _startBrowser(url, settings));
+  Safari(url, {ExecutableSettings? settings})
+      : super(() =>
+            _startBrowser(url, settings ?? defaultSettings[Runtime.safari]!));
 
   /// Starts a new instance of Safari open to the given [url], which may be a
   /// [Uri] or a [String].
   static Future<Process> _startBrowser(url, ExecutableSettings settings) async {
-    settings ??= defaultSettings[Runtime.safari];
     var dir = createTempDir();
 
     // Safari will only open files (not general URLs) via the command-line
diff --git a/pkgs/test/lib/src/runner/executable_settings.dart b/pkgs/test/lib/src/runner/executable_settings.dart
index e89f36e..84a8c5a 100644
--- a/pkgs/test/lib/src/runner/executable_settings.dart
+++ b/pkgs/test/lib/src/runner/executable_settings.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:io';
 
@@ -20,13 +18,13 @@
   ///
   /// This may be an absolute path or a basename, in which case it will be
   /// looked up on the system path. It may not be relative.
-  final String _linuxExecutable;
+  final String? _linuxExecutable;
 
   /// The path to the executable on Mac OS.
   ///
   /// This may be an absolute path or a basename, in which case it will be
   /// looked up on the system path. It may not be relative.
-  final String _macOSExecutable;
+  final String? _macOSExecutable;
 
   /// The path to the executable on Windows.
   ///
@@ -34,15 +32,16 @@
   /// up on the system path; or a relative path, in which case it will be looked
   /// up relative to the paths in the `LOCALAPPDATA`, `PROGRAMFILES`, and
   /// `PROGRAMFILES(X64)` environment variables.
-  final String _windowsExecutable;
+  final String? _windowsExecutable;
 
   /// The path to the executable for the current operating system.
   String get executable {
-    if (Platform.isMacOS) return _macOSExecutable;
-    if (!Platform.isWindows) return _linuxExecutable;
-    if (p.isAbsolute(_windowsExecutable)) return _windowsExecutable;
-    if (p.basename(_windowsExecutable) == _windowsExecutable) {
-      return _windowsExecutable;
+    if (Platform.isMacOS) return _macOSExecutable!;
+    if (!Platform.isWindows) return _linuxExecutable!;
+    final windowsExecutable = _windowsExecutable!;
+    if (p.isAbsolute(windowsExecutable)) return windowsExecutable;
+    if (p.basename(windowsExecutable) == windowsExecutable) {
+      return windowsExecutable;
     }
 
     var prefixes = [
@@ -54,14 +53,14 @@
     for (var prefix in prefixes) {
       if (prefix == null) continue;
 
-      var path = p.join(prefix, _windowsExecutable);
+      var path = p.join(prefix, windowsExecutable);
       if (File(path).existsSync()) return path;
     }
 
     // If we can't find a path that works, return one that doesn't. This will
     // cause an "executable not found" error to surface.
     return p.join(
-        prefixes.firstWhere((prefix) => prefix != null, orElse: () => '.'),
+        prefixes.firstWhere((prefix) => prefix != null, orElse: () => '.')!,
         _windowsExecutable);
   }
 
@@ -69,11 +68,11 @@
   ///
   /// This is currently only supported by Chrome.
   bool get headless => _headless ?? true;
-  final bool _headless;
+  final bool? _headless;
 
   /// Parses settings from a user-provided YAML mapping.
   factory ExecutableSettings.parse(YamlMap settings) {
-    List<String> arguments;
+    List<String>? arguments;
     var argumentsNode = settings.nodes['arguments'];
     if (argumentsNode != null) {
       var value = argumentsNode.value;
@@ -89,9 +88,9 @@
       }
     }
 
-    String linuxExecutable;
-    String macOSExecutable;
-    String windowsExecutable;
+    String? linuxExecutable;
+    String? macOSExecutable;
+    String? windowsExecutable;
     var executableNode = settings.nodes['executable'];
     if (executableNode != null) {
       var value = executableNode.value;
@@ -140,7 +139,7 @@
   ///
   /// If [allowRelative] is `false` (the default), asserts that the value isn't
   /// a relative path.
-  static String _getExecutable(YamlNode executableNode,
+  static String? _getExecutable(YamlNode? executableNode,
       {bool allowRelative = false}) {
     if (executableNode == null || executableNode.value == null) return null;
     if (executableNode.value is! String) {
@@ -166,11 +165,11 @@
   }
 
   ExecutableSettings(
-      {Iterable<String> arguments,
-      String linuxExecutable,
-      String macOSExecutable,
-      String windowsExecutable,
-      bool headless})
+      {Iterable<String>? arguments,
+      String? linuxExecutable,
+      String? macOSExecutable,
+      String? windowsExecutable,
+      bool? headless})
       : arguments = arguments == null ? const [] : List.unmodifiable(arguments),
         _linuxExecutable = linuxExecutable,
         _macOSExecutable = macOSExecutable,
diff --git a/pkgs/test/lib/src/runner/node/platform.dart b/pkgs/test/lib/src/runner/node/platform.dart
index 86cf8e2..7ac0ccc 100644
--- a/pkgs/test/lib/src/runner/node/platform.dart
+++ b/pkgs/test/lib/src/runner/node/platform.dart
@@ -89,8 +89,8 @@
   Future<RunnerSuite> load(String path, SuitePlatform platform,
       SuiteConfiguration suiteConfig, Object message) async {
     var pair = await _loadChannel(path, platform.runtime, suiteConfig);
-    var controller = deserializeSuite(
-        path, platform, suiteConfig, PluginEnvironment(), pair.first, message);
+    var controller = deserializeSuite(path, platform, suiteConfig,
+        PluginEnvironment(), pair.first.cast(), message);
 
     controller.channel('test.node.mapper').sink.add(pair.last?.serialize());
 
@@ -129,7 +129,10 @@
 
       return Pair(channel, pair.last);
     } finally {
-      unawaited(Future.wait(servers.map((s) => s.close().catchError((_) {}))));
+      unawaited(Future.wait<void>(servers.map((s) => s
+          .close()
+          .then<ServerSocket /*?*/ >((v) => v)
+          .onError((_, __) => null))));
     }
   }
 
diff --git a/pkgs/test/lib/src/util/one_off_handler.dart b/pkgs/test/lib/src/util/one_off_handler.dart
index c07799a..7667df3 100644
--- a/pkgs/test/lib/src/util/one_off_handler.dart
+++ b/pkgs/test/lib/src/util/one_off_handler.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'package:path/path.dart' as p;
diff --git a/pkgs/test/lib/src/util/package_map.dart b/pkgs/test/lib/src/util/package_map.dart
index fd39842..6b30afa 100644
--- a/pkgs/test/lib/src/util/package_map.dart
+++ b/pkgs/test/lib/src/util/package_map.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'package:package_config/package_config.dart';
 
diff --git a/pkgs/test/lib/src/util/path_handler.dart b/pkgs/test/lib/src/util/path_handler.dart
index 4b94f1d..ee43351 100644
--- a/pkgs/test/lib/src/util/path_handler.dart
+++ b/pkgs/test/lib/src/util/path_handler.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 import 'package:path/path.dart' as p;
@@ -38,12 +36,12 @@
   }
 
   FutureOr<shelf.Response> _onRequest(shelf.Request request) {
-    shelf.Handler handler;
-    int handlerIndex;
-    var node = _paths;
+    shelf.Handler? handler;
+    int? handlerIndex;
+    _Node? node = _paths;
     var components = p.url.split(request.url.path);
     for (var i = 0; i < components.length; i++) {
-      node = node.children[components[i]];
+      node = node!.children[components[i]];
       if (node == null) break;
       if (node.handler == null) continue;
       handler = node.handler;
@@ -52,13 +50,13 @@
 
     if (handler == null) return shelf.Response.notFound('Not found.');
 
-    return handler(
-        request.change(path: p.url.joinAll(components.take(handlerIndex + 1))));
+    return handler(request.change(
+        path: p.url.joinAll(components.take(handlerIndex! + 1))));
   }
 }
 
 /// A trie node.
 class _Node {
-  shelf.Handler handler;
+  shelf.Handler? handler;
   final children = <String, _Node>{};
 }
diff --git a/pkgs/test/pubspec.yaml b/pkgs/test/pubspec.yaml
index ce998f8..9905a73 100644
--- a/pkgs/test/pubspec.yaml
+++ b/pkgs/test/pubspec.yaml
@@ -7,29 +7,30 @@
   sdk: '>=2.12.0-0 <3.0.0'
 
 dependencies:
-  analyzer: '>=0.39.5 <2.0.0'
+  analyzer: ^1.0.0
   async: ^2.5.0
   boolean_selector: ^2.1.0
-  coverage: '>=0.13.4 <2.0.0'
-  http_multi_server: '>=2.0.0 <4.0.0'
-  io: '>=0.3.0 <2.0.0'
+  collection: ^1.15.0
+  coverage: ^1.0.1
+  http_multi_server: ^3.0.0
+  io: ^1.0.0
   js: ^0.6.3
   node_preamble: ^1.3.0
-  package_config: '>=1.9.0 <3.0.0'
+  package_config: ^2.0.0
   path: ^1.8.0
   pedantic: ^1.10.0
   pool: ^1.5.0
-  shelf: '>=0.7.0 <2.0.0'
-  shelf_packages_handler: '>=1.0.0 <4.0.0'
-  shelf_static: '>=0.2.6 <2.0.0'
-  shelf_web_socket: '>=0.2.0 <2.0.0'
+  shelf: ^1.0.0
+  shelf_packages_handler: ^3.0.0
+  shelf_static: ^1.0.0
+  shelf_web_socket: ^1.0.0
   source_span: ^1.8.0
   stack_trace: ^1.10.0
   stream_channel: ^2.1.0
   typed_data: ^1.3.0
-  web_socket_channel: '>=1.0.0 <3.0.0'
-  webkit_inspection_protocol: '>=0.5.0 <2.0.0'
-  yaml: '>=2.0.0 <4.0.0'
+  web_socket_channel: ^2.0.0
+  webkit_inspection_protocol: ^1.0.0
+  yaml: ^3.0.0
   # Use an exact version until the test_api and test_core package are stable.
   test_api: 0.2.20
   test_core: 0.3.15
@@ -38,7 +39,7 @@
   fake_async: ^1.0.0
   shelf_test_handler: ^2.0.0
   test_descriptor: ^2.0.0
-  test_process: '>=1.0.0 <3.0.0'
+  test_process: ^2.0.0
 
 dependency_overrides:
   test_core:
diff --git a/pkgs/test/test/runner/browser/chrome_test.dart b/pkgs/test/test/runner/browser/chrome_test.dart
index 7912aed..a60bdf4 100644
--- a/pkgs/test/test/runner/browser/chrome_test.dart
+++ b/pkgs/test/test/runner/browser/chrome_test.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 @TestOn('vm')
 @Tags(['chrome'])
diff --git a/pkgs/test/test/runner/browser/firefox_test.dart b/pkgs/test/test/runner/browser/firefox_test.dart
index d49e20b..748eafe 100644
--- a/pkgs/test/test/runner/browser/firefox_test.dart
+++ b/pkgs/test/test/runner/browser/firefox_test.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 @TestOn('vm')
 @Tags(['firefox'])
diff --git a/pkgs/test/test/runner/browser/internet_explorer_test.dart b/pkgs/test/test/runner/browser/internet_explorer_test.dart
index 692162a..e240755 100644
--- a/pkgs/test/test/runner/browser/internet_explorer_test.dart
+++ b/pkgs/test/test/runner/browser/internet_explorer_test.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 @TestOn('vm')
 @Tags(['ie'])
diff --git a/pkgs/test/test/runner/browser/loader_test.dart b/pkgs/test/test/runner/browser/loader_test.dart
index 9ffae9a..e1d6d37 100644
--- a/pkgs/test/test/runner/browser/loader_test.dart
+++ b/pkgs/test/test/runner/browser/loader_test.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 @TestOn('vm')
 @Tags(['chrome'])
@@ -25,7 +23,7 @@
 
 import '../../utils.dart';
 
-Loader _loader;
+late Loader _loader;
 
 /// A configuration that loads suites on Chrome.
 final _chrome =
@@ -56,7 +54,7 @@
   tearDown(() => _loader.close());
 
   group('.loadFile()', () {
-    RunnerSuite suite;
+    late RunnerSuite suite;
     setUp(() async {
       var suites = await _loader
           .loadFile(p.join(d.sandbox, 'a_test.dart'), _chrome)
@@ -64,7 +62,7 @@
 
       expect(suites, hasLength(1));
       var loadSuite = suites.first;
-      suite = await loadSuite.getSuite();
+      suite = (await loadSuite.getSuite())!;
     });
 
     test('returns a suite with the file path and platform', () {
@@ -124,7 +122,7 @@
         .toList();
     expect(suites, hasLength(1));
     var loadSuite = suites.first;
-    var suite = await loadSuite.getSuite();
+    var suite = (await loadSuite.getSuite())!;
     expect(suite.group.entries, hasLength(3));
     expect(suite.group.entries[0].name, equals('success'));
     expect(suite.group.entries[1].name, equals('failure'));
@@ -142,6 +140,7 @@
               RuntimeSelection(Runtime.chrome.identifier)
             ]))
         .asyncMap((loadSuite) => loadSuite.getSuite())
+        .cast<RunnerSuite>()
         .toList();
     expect(suites[0].platform.runtime, equals(Runtime.vm));
     expect(suites[0].path, equals(path));
diff --git a/pkgs/test/test/runner/browser/phantom_js_test.dart b/pkgs/test/test/runner/browser/phantom_js_test.dart
index 3c371b6..f0c4c10 100644
--- a/pkgs/test/test/runner/browser/phantom_js_test.dart
+++ b/pkgs/test/test/runner/browser/phantom_js_test.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 @TestOn('vm')
 @Tags(['phantomjs'])
diff --git a/pkgs/test/test/runner/browser/safari_test.dart b/pkgs/test/test/runner/browser/safari_test.dart
index ed37252..e1f5a1a 100644
--- a/pkgs/test/test/runner/browser/safari_test.dart
+++ b/pkgs/test/test/runner/browser/safari_test.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 @TestOn('vm')
 @Tags(['safari'])
diff --git a/pkgs/test/test/runner/configuration/custom_platform_test.dart b/pkgs/test/test/runner/configuration/custom_platform_test.dart
index 66d5956..371cb8d 100644
--- a/pkgs/test/test/runner/configuration/custom_platform_test.dart
+++ b/pkgs/test/test/runner/configuration/custom_platform_test.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 @TestOn('vm')
 
@@ -84,7 +82,7 @@
           path = await process.stdout.next;
           await process.shouldExit(0);
         } else {
-          path = defaultSettings[Runtime.chrome].executable;
+          path = defaultSettings[Runtime.chrome]!.executable;
         }
 
         await d.file('dart_test.yaml', '''
@@ -482,7 +480,7 @@
           path = await process.stdout.next;
           await process.shouldExit(0);
         } else {
-          path = defaultSettings[Runtime.chrome].executable;
+          path = defaultSettings[Runtime.chrome]!.executable;
         }
 
         await d.file('dart_test.yaml', '''
diff --git a/pkgs/test/test/util/one_off_handler_test.dart b/pkgs/test/test/util/one_off_handler_test.dart
index 31f9fa6..c18f2da 100644
--- a/pkgs/test/test/util/one_off_handler_test.dart
+++ b/pkgs/test/test/util/one_off_handler_test.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 
@@ -11,7 +9,7 @@
 import 'package:test/test.dart';
 
 void main() {
-  OneOffHandler handler;
+  late OneOffHandler handler;
   setUp(() => handler = OneOffHandler());
 
   Future<shelf.Response> _handle(shelf.Request request) =>
diff --git a/pkgs/test/test/util/path_handler_test.dart b/pkgs/test/test/util/path_handler_test.dart
index 1d99337..24daa38 100644
--- a/pkgs/test/test/util/path_handler_test.dart
+++ b/pkgs/test/test/util/path_handler_test.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 import 'dart:async';
 
@@ -11,7 +9,7 @@
 import 'package:test/test.dart';
 
 void main() {
-  PathHandler handler;
+  late PathHandler handler;
   setUp(() => handler = PathHandler());
 
   Future<shelf.Response> _handle(shelf.Request request) =>
@@ -60,7 +58,7 @@
     handler.add(
         'foo',
         expectAsync1((_) {
-          return null;
+          return shelf.Response.notFound('fake');
         }, count: 0));
     handler.add('foo/bar', expectAsync1((request) {
       expect(request.handlerPath, equals('/foo/bar/'));
@@ -70,7 +68,7 @@
     handler.add(
         'foo/bar/baz/bang',
         expectAsync1((_) {
-          return null;
+          return shelf.Response.notFound('fake');
         }, count: 0));
 
     var response = await _handle(request);
diff --git a/pkgs/test/tool/host.dart b/pkgs/test/tool/host.dart
index 8c6c098..c67744b 100644
--- a/pkgs/test/tool/host.dart
+++ b/pkgs/test/tool/host.dart
@@ -1,8 +1,6 @@
 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// @dart=2.7
 
 @JS()
 library test.host;
@@ -23,7 +21,7 @@
 
 /// Returns the current content shell runner, or `null` if none exists.
 @JS()
-external _TestRunner get testRunner;
+external _TestRunner? get testRunner;
 
 /// A class that exposes the test API to JS.
 ///
@@ -114,7 +112,7 @@
   testRunner?.waitUntilDone();
 
   if (_currentUrl.queryParameters['debug'] == 'true') {
-    document.body.classes.add('debug');
+    document.body!.classes.add('debug');
   }
 
   runZonedGuarded(() {
@@ -127,14 +125,14 @@
             _connectToIframe(message['url'] as String, message['id'] as int);
         suiteChannel.pipe(iframeChannel);
       } else if (message['command'] == 'displayPause') {
-        document.body.classes.add('paused');
+        document.body!.classes.add('paused');
       } else if (message['command'] == 'resume') {
-        document.body.classes.remove('paused');
+        document.body!.classes.remove('paused');
       } else {
         assert(message['command'] == 'closeSuite');
-        _iframes.remove(message['id']).remove();
+        _iframes.remove(message['id'])!.remove();
 
-        for (var subscription in _subscriptions.remove(message['id'])) {
+        for (var subscription in _subscriptions.remove(message['id'])!) {
           subscription.cancel();
         }
       }
@@ -146,13 +144,13 @@
         (_) => serverChannel.sink.add({'command': 'ping'}));
 
     var play = document.querySelector('#play');
-    play.onClick.listen((_) {
-      if (!document.body.classes.remove('paused')) return;
+    play!.onClick.listen((_) {
+      if (!document.body!.classes.remove('paused')) return;
       serverChannel.sink.add({'command': 'resume'});
     });
 
     _jsApi = _JSApi(resume: allowInterop(() {
-      if (!document.body.classes.remove('paused')) return;
+      if (!document.body!.classes.remove('paused')) return;
       serverChannel.sink.add({'command': 'resume'});
     }), restartCurrent: allowInterop(() {
       serverChannel.sink.add({'command': 'restart'});
@@ -167,7 +165,7 @@
 MultiChannel<dynamic> _connectToServer() {
   // The `managerUrl` query parameter contains the WebSocket URL of the remote
   // [BrowserManager] with which this communicates.
-  var webSocket = WebSocket(_currentUrl.queryParameters['managerUrl']);
+  var webSocket = WebSocket(_currentUrl.queryParameters['managerUrl']!);
 
   var controller = StreamChannelController(sync: true);
   webSocket.onMessage.listen((message) {
@@ -188,7 +186,7 @@
   var iframe = IFrameElement();
   _iframes[id] = iframe;
   iframe.src = url;
-  document.body.children.add(iframe);
+  document.body!.children.add(iframe);
 
   // Use this to communicate securely with the iframe.
   var channel = MessageChannel();
@@ -217,7 +215,7 @@
     if (message.data['ready'] == true) {
       // This message indicates that the iframe is actively listening for
       // events, so the message channel's second port can now be transferred.
-      iframe.contentWindow
+      iframe.contentWindow!
           .postMessage('port', window.location.origin, [channel.port2]);
       readyCompleter.complete();
     } else if (message.data['exception'] == true) {
diff --git a/pkgs/test_api/lib/src/utils.dart b/pkgs/test_api/lib/src/utils.dart
index d0bb22e..8396941 100644
--- a/pkgs/test_api/lib/src/utils.dart
+++ b/pkgs/test_api/lib/src/utils.dart
@@ -231,7 +231,7 @@
   for (var operation in operationSet) {
     operation.value
         .then((value) => controller.add(value))
-        .catchError(controller.addError)
+        .onError(controller.addError)
         .whenComplete(() {
       operationSet.remove(operation);
       if (operationSet.isEmpty) controller.close();
diff --git a/pkgs/test_core/lib/src/runner/engine.dart b/pkgs/test_core/lib/src/runner/engine.dart
index 0366689..27af38d 100644
--- a/pkgs/test_core/lib/src/runner/engine.dart
+++ b/pkgs/test_core/lib/src/runner/engine.dart
@@ -216,7 +216,7 @@
       _onTestStartedGroup.close();
       _onSuiteStartedController.close();
       _closedBeforeDone ??= false;
-    }).catchError((_) {
+    }).onError((_, __) {
       // Don't top-level errors. They'll be thrown via [success] anyway.
     });
   }
diff --git a/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart b/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart
index fbd0b73..d2b1120 100644
--- a/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart
+++ b/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart
@@ -42,13 +42,13 @@
     SuitePlatform platform,
     SuiteConfiguration suiteConfig,
     Environment environment,
-    StreamChannel channel,
+    StreamChannel<Object?> channel,
     Object message,
     {Future<Map<String, dynamic>> Function()? gatherCoverage}) {
-  var disconnector = Disconnector();
-  var suiteChannel = MultiChannel(channel.transform(disconnector));
+  var disconnector = Disconnector<Object?>();
+  var suiteChannel = MultiChannel<Object?>(channel.transform(disconnector));
 
-  suiteChannel.sink.add(<String, dynamic>{
+  suiteChannel.sink.add(<String, Object?>{
     'type': 'initial',
     'platform': platform.serialize(),
     'metadata': suiteConfig.metadata.serialize(),
@@ -77,7 +77,7 @@
     }
   }
 
-  suiteChannel.stream.listen(
+  suiteChannel.stream.cast<Map<String, Object?>>().listen(
       (response) {
         switch (response['type'] as String) {
           case 'print':
@@ -113,7 +113,7 @@
   return RunnerSuiteController(
       environment, suiteConfig, suiteChannel, completer.future, platform,
       path: path,
-      onClose: () => disconnector.disconnect().catchError(handleError),
+      onClose: () => disconnector.disconnect().onError(handleError),
       gatherCoverage: gatherCoverage);
 }
 
diff --git a/pkgs/test_core/lib/src/runner/vm/platform.dart b/pkgs/test_core/lib/src/runner/vm/platform.dart
index e79ee7a..989feec 100644
--- a/pkgs/test_core/lib/src/runner/vm/platform.dart
+++ b/pkgs/test_core/lib/src/runner/vm/platform.dart
@@ -89,7 +89,7 @@
     environment ??= PluginEnvironment();
 
     var controller = deserializeSuite(
-        path, platform, suiteConfig, environment, channel, message,
+        path, platform, suiteConfig, environment, channel.cast(), message,
         gatherCoverage: () => _gatherCoverage(environment!));
 
     if (isolateRef != null) {