[flutter_plugin_tools] Work around banner in drive-examples (#4142)

* [flutter_plugin_tools] Work around banner in drive-examples

Strip off any unexpected output before parsing `flutter devices
--machine` output. Works around a bug in the Flutter tool that can
result in banners being printed in `--machine` mode.

Fixes https://github.com/flutter/flutter/issues/86052
diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md
index 9e9538c..3f31a49 100644
--- a/script/tool/CHANGELOG.md
+++ b/script/tool/CHANGELOG.md
@@ -9,6 +9,7 @@
   immediately abort the test.
 - Deprecated `--plugins` in favor of new `--packages`. `--plugins` continues to
   work for now, but will be removed in the future.
+- Make `drive-examples` device detection robust against Flutter tool banners.
 
 ## 0.3.0
 
diff --git a/script/tool/lib/src/drive_examples_command.dart b/script/tool/lib/src/drive_examples_command.dart
index 1e19535..df74119 100644
--- a/script/tool/lib/src/drive_examples_command.dart
+++ b/script/tool/lib/src/drive_examples_command.dart
@@ -208,9 +208,14 @@
       return deviceIds;
     }
 
+    String output = result.stdout as String;
+    // --machine doesn't currently prevent the tool from printing banners;
+    // see https://github.com/flutter/flutter/issues/86055. This workaround
+    // can be removed once that is fixed.
+    output = output.substring(output.indexOf('['));
+
     final List<Map<String, dynamic>> devices =
-        (jsonDecode(result.stdout as String) as List<dynamic>)
-            .cast<Map<String, dynamic>>();
+        (jsonDecode(output) as List<dynamic>).cast<Map<String, dynamic>>();
     for (final Map<String, dynamic> deviceInfo in devices) {
       final String targetPlatform =
           (deviceInfo['targetPlatform'] as String?) ?? '';
diff --git a/script/tool/test/drive_examples_command_test.dart b/script/tool/test/drive_examples_command_test.dart
index d22d95f..681a9e0 100644
--- a/script/tool/test/drive_examples_command_test.dart
+++ b/script/tool/test/drive_examples_command_test.dart
@@ -44,13 +44,22 @@
     void setMockFlutterDevicesOutput({
       bool hasIosDevice = true,
       bool hasAndroidDevice = true,
+      bool includeBanner = false,
     }) {
+      const String updateBanner = '''
+╔════════════════════════════════════════════════════════════════════════════╗
+║ A new version of Flutter is available!                                     ║
+║                                                                            ║
+║ To update to the latest version, run "flutter upgrade".                    ║
+╚════════════════════════════════════════════════════════════════════════════╝
+''';
       final List<String> devices = <String>[
         if (hasIosDevice) '{"id": "$_fakeIosDevice", "targetPlatform": "ios"}',
         if (hasAndroidDevice)
           '{"id": "$_fakeAndroidDevice", "targetPlatform": "android-x86"}',
       ];
-      final String output = '''[${devices.join(',')}]''';
+      final String output =
+          '''${includeBanner ? updateBanner : ''}[${devices.join(',')}]''';
 
       final MockProcess mockDevicesProcess = MockProcess.succeeding();
       mockDevicesProcess.stdoutController.close(); // ignore: unawaited_futures
@@ -113,6 +122,32 @@
       );
     });
 
+    test('handles flutter tool banners when checking devices', () async {
+      createFakePlugin(
+        'plugin',
+        packagesDir,
+        extraFiles: <String>[
+          'example/test_driver/integration_test.dart',
+          'example/integration_test/foo_test.dart',
+        ],
+        platformSupport: <String, PlatformSupport>{
+          kPlatformIos: PlatformSupport.inline,
+        },
+      );
+
+      setMockFlutterDevicesOutput(includeBanner: true);
+      final List<String> output =
+          await runCapturingPrint(runner, <String>['drive-examples', '--ios']);
+
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('Running for plugin'),
+          contains('No issues found!'),
+        ]),
+      );
+    });
+
     test('fails for iOS if getting devices fails', () async {
       // Simulate failure from `flutter devices`.
       processRunner.mockProcessesForExecutable['flutter'] = <io.Process>[