Add assert that the root widget has been attached. (#32437)

diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart
index 3654918..bf91401 100644
--- a/packages/flutter/lib/src/widgets/binding.dart
+++ b/packages/flutter/lib/src/widgets/binding.dart
@@ -736,6 +736,12 @@
     ).attachToRenderTree(buildOwner, renderViewElement);
   }
 
+  /// Whether the [renderViewElement] has been initialized.
+  ///
+  /// This will be false until [runApp] is called (or [WidgetTester.pumpWidget]
+  /// is called in the context of a [TestWidgetsFlutterBinding]).
+  bool get isRootWidgetAttached => _renderViewElement != null;
+
   @override
   Future<void> performReassemble() {
     assert(() {
diff --git a/packages/flutter_driver/lib/src/extension/extension.dart b/packages/flutter_driver/lib/src/extension/extension.dart
index 894d6f9..1e1f1b6 100644
--- a/packages/flutter_driver/lib/src/extension/extension.dart
+++ b/packages/flutter_driver/lib/src/extension/extension.dart
@@ -175,6 +175,8 @@
   /// the result into a subclass of [Result], but that's not strictly required.
   @visibleForTesting
   Future<Map<String, dynamic>> call(Map<String, String> params) async {
+    assert(WidgetsBinding.instance.isRootWidgetAttached,
+        'No root widget is attached; have you remembered to call runApp()?');
     final String commandKind = params['command'];
     try {
       final CommandHandlerCallback commandHandler = _commandHandlers[commandKind];