diff --git a/pkgs/test/lib/src/runner/browser/compilers/dart2js.dart b/pkgs/test/lib/src/runner/browser/compilers/dart2js.dart
index 3cd67453b..9ef1f4e1f 100644
--- a/pkgs/test/lib/src/runner/browser/compilers/dart2js.dart
+++ b/pkgs/test/lib/src/runner/browser/compilers/dart2js.dart
@@ -112,13 +112,14 @@ class Dart2JsSupport extends CompilerSupport with JsHtmlWrapper {
       var jsPath = p.join(dir, '${p.basename(dartPath)}.browser_test.dart.js');
       var bootstrapContent = '''
         ${suiteConfig.metadata.languageVersionComment ?? await rootPackageLanguageVersionComment}
+        import 'dart:js_interop';
         import 'package:test/src/bootstrap/browser.dart';
         import 'package:test/src/runner/browser/dom.dart' as dom;
 
         import '${await absoluteUri(dartPath)}' as test;
 
         void main() {
-          dom.window.console.log(r'Startup for test path $dartPath');
+          dom.window.console.log(r'Startup for test path $dartPath'.toJS);
           internalBootstrapBrowserTest(() => test.main);
         }
       ''';
diff --git a/pkgs/test/lib/src/runner/browser/dom.dart b/pkgs/test/lib/src/runner/browser/dom.dart
index a3a2d96bb..fe6a658a4 100644
--- a/pkgs/test/lib/src/runner/browser/dom.dart
+++ b/pkgs/test/lib/src/runner/browser/dom.dart
@@ -2,64 +2,51 @@
 // 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.
 
-// ignore: deprecated_member_use
-import 'dart:js_util' as js_util;
+import 'dart:js_interop';
+import 'dart:js_interop_unsafe';
 
-// ignore: deprecated_member_use
-import 'package:js/js.dart';
-
-@JS()
-@staticInterop
-class Window extends EventTarget {}
-
-extension WindowExtension on Window {
+extension type Window(EventTarget _) implements EventTarget {
   @pragma('dart2js:as:trust')
-  Window get parent => js_util.getProperty<dynamic>(this, 'parent') as Window;
+  Window get parent => getProperty('parent'.toJS) as Window;
+
   external Location get location;
-  Console get console => js_util.getProperty(this, 'console') as Console;
+
+  Console get console => getProperty('console'.toJS) as Console;
+
   CSSStyleDeclaration? getComputedStyle(Element elt, [String? pseudoElt]) =>
-      js_util.callMethod(this, 'getComputedStyle', <Object>[
+      callMethodVarArgs('getComputedStyle'.toJS, <JSAny?>[
         elt,
-        if (pseudoElt != null) pseudoElt
+        if (pseudoElt != null) pseudoElt.toJS
       ]) as CSSStyleDeclaration?;
+
   external Navigator get navigator;
+
   void postMessage(Object message, String targetOrigin,
           [List<MessagePort>? messagePorts]) =>
-      js_util.callMethod(this, 'postMessage', <Object?>[
-        js_util.jsify(message),
-        targetOrigin,
-        if (messagePorts != null) js_util.jsify(messagePorts)
+      callMethodVarArgs('postMessage'.toJS, <JSAny?>[
+        message.jsify(),
+        targetOrigin.toJS,
+        if (messagePorts != null) messagePorts.toJS
       ]);
 }
 
 @JS('window')
 external Window get window;
 
-@JS()
-@staticInterop
-class Console {}
-
-extension ConsoleExtension on Console {
-  external void log(Object? object);
-  external void warn(Object? object);
+extension type Console(JSObject _) implements JSObject {
+  external void log(JSAny? object);
+  external void warn(JSAny? object);
 }
 
-@JS()
-@staticInterop
-class Document extends Node {}
-
-extension DocumentExtension on Document {
+extension type Document(Node _) implements Node {
   external Element? querySelector(String selectors);
-  Element createElement(String name, [Object? options]) => js_util.callMethod(
-          this, 'createElement', <Object>[name, if (options != null) options])
-      as Element;
-}
 
-@JS()
-@staticInterop
-class HTMLDocument extends Document {}
+  Element createElement(String name, [Object? options]) => callMethodVarArgs(
+      'createElement'.toJS,
+      <JSAny?>[name.toJS, if (options != null) options.jsify()]) as Element;
+}
 
-extension HTMLDocumentExtension on HTMLDocument {
+extension type HTMLDocument(Document _) implements Document {
   external HTMLBodyElement? get body;
   external String? get title;
 }
@@ -67,35 +54,19 @@ extension HTMLDocumentExtension on HTMLDocument {
 @JS('document')
 external HTMLDocument get document;
 
-@JS()
-@staticInterop
-class Navigator {}
-
-extension NavigatorExtension on Navigator {
+extension type Navigator(JSObject _) implements JSObject {
   external String get userAgent;
 }
 
-@JS()
-@staticInterop
-class Element extends Node {}
-
-extension DomElementExtension on Element {
+extension type Element(Node _) implements Node {
   external DomTokenList get classList;
 }
 
-@JS()
-@staticInterop
-class HTMLElement extends Element {}
-
-@JS()
-@staticInterop
-class HTMLBodyElement extends HTMLElement {}
+extension type HTMLElement(Element _) implements Element {}
 
-@JS()
-@staticInterop
-class Node extends EventTarget {}
+extension type HTMLBodyElement(HTMLElement _) implements HTMLElement {}
 
-extension NodeExtension on Node {
+extension type Node(EventTarget _) implements EventTarget {
   external Node appendChild(Node node);
   void remove() {
     if (parentNode != null) {
@@ -108,47 +79,43 @@ extension NodeExtension on Node {
   external Node? get parentNode;
 }
 
-@JS()
-@staticInterop
-class EventTarget {}
-
-extension EventTargetExtension on EventTarget {
+extension type EventTarget(JSObject _) implements JSObject {
   void addEventListener(String type, EventListener? listener,
       [bool? useCapture]) {
     if (listener != null) {
-      js_util.callMethod<void>(this, 'addEventListener',
-          <Object>[type, listener, if (useCapture != null) useCapture]);
+      callMethodVarArgs('addEventListener'.toJS, <JSAny?>[
+        type.toJS,
+        listener.toJS,
+        if (useCapture != null) useCapture.toJS
+      ]);
     }
   }
 
   void removeEventListener(String type, EventListener? listener,
       [bool? useCapture]) {
     if (listener != null) {
-      js_util.callMethod<void>(this, 'removeEventListener',
-          <Object>[type, listener, if (useCapture != null) useCapture]);
+      callMethodVarArgs('removeEventListener'.toJS, <JSAny?>[
+        type.toJS,
+        listener.toJS,
+        if (useCapture != null) useCapture.toJS
+      ]);
     }
   }
 }
 
 typedef EventListener = void Function(Event event);
 
-@JS()
-@staticInterop
-class Event {}
-
-extension EventExtension on Event {
+extension type Event(JSObject _) implements JSObject {
   external void stopPropagation();
 }
 
-@JS()
-@staticInterop
-class MessageEvent extends Event {}
+extension type MessageEvent(Event _) implements Event {
+  dynamic get data => getProperty('data'.toJS).dartify();
 
-extension MessageEventExtension on MessageEvent {
-  dynamic get data => js_util.dartify(js_util.getProperty(this, 'data'));
   external String get origin;
+
   List<MessagePort> get ports =>
-      js_util.getProperty<List>(this, 'ports').cast<MessagePort>();
+      getProperty<JSArray>('ports'.toJS).toDart.cast<MessagePort>();
 
   /// The source may be a `WindowProxy`, a `MessagePort`, or a `ServiceWorker`.
   ///
@@ -156,77 +123,47 @@ extension MessageEventExtension on MessageEvent {
   /// the source will be a `WindowProxy` which has the same methods as [Window].
   @pragma('dart2js:as:trust')
   MessageEventSource get source =>
-      js_util.getProperty<dynamic>(this, 'source') as MessageEventSource;
+      getProperty('source'.toJS) as MessageEventSource;
 }
 
-@JS()
-@staticInterop
-class MessageEventSource {}
-
-extension MessageEventSourceExtension on MessageEventSource {
+extension type MessageEventSource(JSObject _) implements JSObject {
   @pragma('dart2js:as:trust')
   MessageEventSourceLocation? get location =>
-      js_util.getProperty<dynamic>(this, 'location')
-          as MessageEventSourceLocation;
+      getProperty('location'.toJS) as MessageEventSourceLocation;
 }
 
-@JS()
-@staticInterop
-class MessageEventSourceLocation {}
-
-extension MessageEventSourceLocationExtension on MessageEventSourceLocation {
+extension type MessageEventSourceLocation(JSObject _) implements JSObject {
   external String? get href;
 }
 
-@JS()
-@staticInterop
-class Location {}
-
-extension LocationExtension on Location {
+extension type Location(JSObject _) implements JSObject {
   external String get href;
   external String get origin;
 }
 
-@JS()
-@staticInterop
-class MessagePort extends EventTarget {}
+extension type MessagePort(EventTarget _) implements EventTarget {
+  void postMessage(Object? message) => callMethodVarArgs(
+      'postMessage'.toJS, <JSAny?>[if (message != null) message.jsify()]);
 
-extension MessagePortExtension on MessagePort {
-  void postMessage(Object? message) => js_util.callMethod(this, 'postMessage',
-      <Object>[if (message != null) js_util.jsify(message) as Object]);
   external void start();
 }
 
-@JS()
-@staticInterop
-class CSSStyleDeclaration {}
+extension type CSSStyleDeclaration(JSObject _) implements JSObject {}
 
-@JS()
-@staticInterop
-class HTMLScriptElement extends HTMLElement {}
-
-extension HTMLScriptElementExtension on HTMLScriptElement {
+extension type HTMLScriptElement(HTMLElement _) implements HTMLElement {
   external set src(String value);
 }
 
 HTMLScriptElement createHTMLScriptElement() =>
     document.createElement('script') as HTMLScriptElement;
 
-@JS()
-@staticInterop
-class DomTokenList {}
-
-extension DomTokenListExtension on DomTokenList {
+extension type DomTokenList(JSObject _) implements JSObject {
   external void add(String value);
   external void remove(String value);
   external bool contains(String token);
 }
 
-@JS()
-@staticInterop
-class HTMLIFrameElement extends HTMLElement {}
-
-extension HTMLIFrameElementExtension on HTMLIFrameElement {
+extension type HTMLIFrameElement(HTMLElement _) implements HTMLElement {
   external String? get src;
   external set src(String? value);
   external Window get contentWindow;
@@ -235,38 +172,28 @@ extension HTMLIFrameElementExtension on HTMLIFrameElement {
 HTMLIFrameElement createHTMLIFrameElement() =>
     document.createElement('iframe') as HTMLIFrameElement;
 
-@JS()
-@staticInterop
-class WebSocket extends EventTarget {}
-
-extension WebSocketExtension on WebSocket {
-  external void send(Object? data);
+extension type WebSocket(EventTarget _) implements EventTarget {
+  external void send(JSAny? data);
 }
 
 WebSocket createWebSocket(String url) =>
-    _callConstructor('WebSocket', <Object>[url])! as WebSocket;
+    _callConstructor('WebSocket', <JSAny?>[url.toJS])! as WebSocket;
 
-@JS()
-@staticInterop
-class MessageChannel {}
-
-extension MessageChannelExtension on MessageChannel {
+extension type MessageChannel(JSObject _) implements JSObject {
   external MessagePort get port1;
   external MessagePort get port2;
 }
 
 MessageChannel createMessageChannel() =>
-    _callConstructor('MessageChannel', <Object>[])! as MessageChannel;
-
-Object? _findConstructor(String constructorName) =>
-    js_util.getProperty(window, constructorName);
+    _callConstructor('MessageChannel', <JSAny?>[])! as MessageChannel;
 
-Object? _callConstructor(String constructorName, List<Object?> args) {
-  final constructor = _findConstructor(constructorName);
+Object? _callConstructor(String constructorName, List<JSAny?> args) {
+  final constructor = window.getProperty(constructorName.toJS) as JSFunction?;
   if (constructor == null) {
     return null;
   }
-  return js_util.callConstructor(constructor, args);
+
+  return constructor.callAsConstructorVarArgs(args);
 }
 
 class Subscription {
diff --git a/pkgs/test/lib/src/runner/browser/post_message_channel.dart b/pkgs/test/lib/src/runner/browser/post_message_channel.dart
index 29bb3e547..229a33cd5 100644
--- a/pkgs/test/lib/src/runner/browser/post_message_channel.dart
+++ b/pkgs/test/lib/src/runner/browser/post_message_channel.dart
@@ -2,8 +2,7 @@
 // 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.
 
-// ignore: deprecated_member_use
-import 'dart:js_util';
+import 'dart:js_interop';
 
 import 'package:stream_channel/stream_channel.dart';
 
@@ -14,15 +13,15 @@ import 'dom.dart' as dom;
 ///
 /// Sends a [MessagePort] to the host page for the channel.
 StreamChannel<Object?> postMessageChannel() {
-  dom.window.console.log('Suite starting, sending channel to host');
+  dom.window.console.log('Suite starting, sending channel to host'.toJS);
   var controller = StreamChannelController<Object?>(sync: true);
   var channel = dom.createMessageChannel();
   dom.window.parent
       .postMessage('port', dom.window.location.origin, [channel.port2]);
-  var portSubscription = dom.Subscription(channel.port1, 'message',
-      allowInterop((dom.Event event) {
+  var portSubscription =
+      dom.Subscription(channel.port1, 'message', (dom.Event event) {
     controller.local.sink.add((event as dom.MessageEvent).data);
-  }));
+  });
   channel.port1.start();
 
   controller.local.stream
diff --git a/pkgs/test/test/runner/coverage_test.dart b/pkgs/test/test/runner/coverage_test.dart
index b149345e4..a49124ba8 100644
--- a/pkgs/test/test/runner/coverage_test.dart
+++ b/pkgs/test/test/runner/coverage_test.dart
@@ -83,9 +83,8 @@ void main() {
 
       await d.file('js_with_unicode_test.dart', '''
         import 'dart:async';
-
-        import 'package:js/js.dart';
-        import 'package:js/js_util.dart';
+        import 'dart:js_interop';
+        import 'dart:js_interop_unsafe';
 
         import 'package:test/src/runner/browser/dom.dart' as dom;
         import 'package:test/test.dart';
@@ -95,9 +94,9 @@ void main() {
           final scriptLoaded = controller.stream.first;
           final script = dom.createHTMLScriptElement()..src = src;
           script.addEventListener('load',
-              allowInterop((_) {
+              (_) {
                 controller.add('loaded');
-              }));
+              });
           dom.document.body!.appendChild(script);
           await scriptLoaded.timeout(Duration(seconds: 1));
         }
@@ -105,8 +104,8 @@ void main() {
         void main() {
           test("test 1", () async {
             await loadScript('file_with_unicode.js');
-            expect(getProperty(dom.window, 'foo'), isNotNull);
-            callMethod(dom.window, 'foo', []);
+            expect(dom.window.getProperty('foo'.toJS), isNotNull);
+            dom.window.callMethodVarArgs('foo'.toJS, []);
             expect(true, isTrue);
           });
         }
diff --git a/pkgs/test/test/runner/test_on_test.dart b/pkgs/test/test/runner/test_on_test.dart
index 96baf0ba4..f89d92f0a 100644
--- a/pkgs/test/test/runner/test_on_test.dart
+++ b/pkgs/test/test/runner/test_on_test.dart
@@ -201,7 +201,7 @@ Future<void> _writeTestFile(String filename,
     bool loadable = true}) {
   var buffer = StringBuffer();
   if (suiteTestOn != null) buffer.writeln("@TestOn('$suiteTestOn')");
-  if (!loadable) buffer.writeln("import 'dart:js_util';");
+  if (!loadable) buffer.writeln("import 'dart:js_interop';");
 
   buffer
     ..writeln("import 'package:test/test.dart';")
diff --git a/pkgs/test/tool/host.dart b/pkgs/test/tool/host.dart
index fd5a9418d..fe8c61448 100644
--- a/pkgs/test/tool/host.dart
+++ b/pkgs/test/tool/host.dart
@@ -7,9 +7,8 @@ library;
 
 import 'dart:async';
 import 'dart:convert';
+import 'dart:js_interop';
 
-// ignore: deprecated_member_use
-import 'package:js/js.dart';
 import 'package:stack_trace/stack_trace.dart';
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/src/runner/browser/dom.dart' as dom;
@@ -104,7 +103,7 @@ final _currentUrl = Uri.parse(dom.window.location.href);
 /// does mean that the server needs to be sure to nest its [MultiChannel]s at
 /// the same place the client does.
 void main() {
-  dom.window.console.log('Dart test runner browser host running');
+  dom.window.console.log('Dart test runner browser host running'.toJS);
   if (_currentUrl.queryParameters['debug'] == 'true') {
     dom.document.body!.classList.add('debug');
   }
@@ -132,7 +131,7 @@ void main() {
           _domSubscriptions.remove(id)?.cancel();
         default:
           dom.window.console
-              .warn('Unhandled message from test runner: $message');
+              .warn('Unhandled message from test runner: $message'.toJS);
       }
     });
 
@@ -142,21 +141,21 @@ void main() {
         (_) => serverChannel.sink.add({'command': 'ping'}));
 
     var play = dom.document.querySelector('#play');
-    play!.addEventListener('click', allowInterop((_) {
+    play!.addEventListener('click', (_) {
       if (!dom.document.body!.classList.contains('paused')) return;
       dom.document.body!.classList.remove('paused');
       serverChannel.sink.add({'command': 'resume'});
-    }));
+    });
 
-    _jsApi = _JSApi(resume: allowInterop(() {
+    _jsApi = _JSApi(resume: () {
       if (!dom.document.body!.classList.contains('paused')) return;
       dom.document.body!.classList.remove('paused');
       serverChannel.sink.add({'command': 'resume'});
-    }), restartCurrent: allowInterop(() {
+    }, restartCurrent: () {
       serverChannel.sink.add({'command': 'restart'});
-    }));
+    });
   }, (error, stackTrace) {
-    dom.window.console.warn('$error\n${Trace.from(stackTrace).terse}');
+    dom.window.console.warn('$error\n${Trace.from(stackTrace).terse}'.toJS);
   });
 }
 
@@ -169,13 +168,13 @@ MultiChannel<dynamic> _connectToServer() {
       dom.createWebSocket(_currentUrl.queryParameters['managerUrl']!);
 
   var controller = StreamChannelController<Object?>(sync: true);
-  webSocket.addEventListener('message', allowInterop((message) {
+  webSocket.addEventListener('message', (message) {
     controller.local.sink
         .add(jsonDecode((message as dom.MessageEvent).data as String));
-  }));
+  });
 
   controller.local.stream
-      .listen((message) => webSocket.send(jsonEncode(message)));
+      .listen((message) => webSocket.send(jsonEncode(message).toJS));
 
   return MultiChannel(controller.foreign);
 }
@@ -199,14 +198,14 @@ MultiChannel<dynamic> _connectToServer() {
 /// message channel port is active.
 StreamChannel<dynamic> _connectToIframe(String url, int id) {
   var suiteUrl = Uri.parse(url).removeFragment();
-  dom.window.console.log('Starting suite $suiteUrl');
+  dom.window.console.log('Starting suite $suiteUrl'.toJS);
   var iframe = dom.createHTMLIFrameElement();
   _iframes[id] = iframe;
   var controller = StreamChannelController<Object?>(sync: true);
 
   late dom.Subscription windowSubscription;
   windowSubscription =
-      dom.Subscription(dom.window, 'message', allowInterop((dom.Event event) {
+      dom.Subscription(dom.window, 'message', (dom.Event event) {
     // A message on the Window can theoretically come from any website. It's
     // very unlikely that a malicious site would care about hacking someone's
     // unit tests, let alone be able to find the test server while it's
@@ -222,14 +221,13 @@ StreamChannel<dynamic> _connectToIframe(String url, int id) {
 
     switch (message.data) {
       case 'port':
-        dom.window.console.log('Connecting channel for suite $suiteUrl');
+        dom.window.console.log('Connecting channel for suite $suiteUrl'.toJS);
         // The frame is starting and sending a port to forward for the suite.
         final port = message.ports.first;
         assert(!_domSubscriptions.containsKey(id));
-        _domSubscriptions[id] =
-            dom.Subscription(port, 'message', allowInterop((event) {
+        _domSubscriptions[id] = dom.Subscription(port, 'message', (event) {
           controller.local.sink.add((event as dom.MessageEvent).data);
-        }));
+        });
         port.start();
 
         assert(!_subscriptions.containsKey(id));
@@ -239,11 +237,11 @@ StreamChannel<dynamic> _connectToIframe(String url, int id) {
         // loading the test.
         controller.local.sink.add(data);
     }
-  }));
+  });
 
   iframe.src = url;
   dom.document.body!.appendChild(iframe);
-  dom.window.console.log('Appended iframe with src $url');
+  dom.window.console.log('Appended iframe with src $url'.toJS);
 
   return controller.foreign;
 }