Fix stack trace parsing on non-debug builds; add e2e tests (#50652)

* Fix stack trace parsing on non-debug builds; add e2e tests
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index f40fc27..15fa50a 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -338,6 +338,9 @@
 
   DebuggingOptions _createDebuggingOptions() {
     final BuildInfo buildInfo = getBuildInfo();
+    final int browserDebugPort = featureFlags.isWebEnabled && argResults.wasParsed('web-browser-debug-port')
+      ? int.parse(stringArg('web-browser-debug-port'))
+      : null;
     if (buildInfo.mode.isRelease) {
       return DebuggingOptions.disabled(
         buildInfo,
@@ -345,6 +348,8 @@
         hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
         port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
         webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
+        webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
+        webBrowserDebugPort: browserDebugPort,
       );
     } else {
       return DebuggingOptions.enabled(
@@ -367,6 +372,8 @@
         hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
         port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
         webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
+        webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
+        webBrowserDebugPort: browserDebugPort,
         vmserviceOutFile: stringArg('vmservice-out-file'),
         // Allow forcing fast-start to off to prevent doing more work on devices that
         // don't support it.
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index c2f33fc..b3dd7d1 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -536,6 +536,8 @@
     this.hostname,
     this.port,
     this.webEnableExposeUrl,
+    this.webRunHeadless = false,
+    this.webBrowserDebugPort,
     this.vmserviceOutFile,
     this.fastStart = false,
    }) : debuggingEnabled = true;
@@ -545,6 +547,8 @@
       this.port,
       this.hostname,
       this.webEnableExposeUrl,
+      this.webRunHeadless = false,
+      this.webBrowserDebugPort,
       this.cacheSkSL = false,
     }) : debuggingEnabled = false,
       useTestFonts = false,
@@ -585,6 +589,17 @@
   final String port;
   final String hostname;
   final bool webEnableExposeUrl;
+
+  /// Whether to run the browser in headless mode.
+  ///
+  /// Some CI environments do not provide a display and fail to launch the
+  /// browser with full graphics stack. Some browsers provide a special
+  /// "headless" mode that runs the browser with no graphics.
+  final bool webRunHeadless;
+
+  /// The port the browser should use for its debugging protocol.
+  final int webBrowserDebugPort;
+
   /// A file where the vmservice URL should be written after the application is started.
   final String vmserviceOutFile;
   final bool fastStart;
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 397e438..a17cb74 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -173,6 +173,19 @@
         'when running on remote machines.',
       hide: hide,
     );
+    argParser.addFlag('web-run-headless',
+      defaultsTo: false,
+      help: 'Launches the browser in headless mode. Currently only Chrome '
+        'supports this option.',
+      hide: true,
+    );
+    argParser.addOption('web-browser-debug-port',
+      help: 'The debug port the browser should use. If not specified, a '
+        'random port is selected. Currently only Chrome supports this option. '
+        'It serves the Chrome DevTools Protocol '
+        '(https://chromedevtools.github.io/devtools-protocol/).',
+      hide: true,
+    );
   }
 
   void usesTargetOption() {
diff --git a/packages/flutter_tools/lib/src/web/chrome.dart b/packages/flutter_tools/lib/src/web/chrome.dart
index 99953cf..f1e67c6 100644
--- a/packages/flutter_tools/lib/src/web/chrome.dart
+++ b/packages/flutter_tools/lib/src/web/chrome.dart
@@ -97,8 +97,11 @@
   /// `headless` defaults to false, and controls whether we open a headless or
   /// a `headfull` browser.
   ///
+  /// `debugPort` is Chrome's debugging protocol port. If null, a random free
+  /// port is picked automatically.
+  ///
   /// `skipCheck` does not attempt to make a devtools connection before returning.
-  Future<Chrome> launch(String url, { bool headless = false, bool skipCheck = false, Directory dataDir }) async {
+  Future<Chrome> launch(String url, { bool headless = false, int debugPort, bool skipCheck = false, Directory dataDir }) async {
     // This is a JSON file which contains configuration from the
     // browser session, such as window position. It is located
     // under the Chrome data-dir folder.
@@ -117,7 +120,7 @@
       }
     }
 
-    final int port = await globals.os.findFreePort();
+    final int port = debugPort ?? await globals.os.findFreePort();
     final List<String> args = <String>[
       chromeExecutable,
       // Using a tmp directory ensures that a new instance of chrome launches
diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart
index 518d72b..cf48542 100644
--- a/packages/flutter_tools/lib/src/web/web_device.dart
+++ b/packages/flutter_tools/lib/src/web/web_device.dart
@@ -134,10 +134,14 @@
     // See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
     // for the web initialization and server logic.
     final String url = platformArgs['uri'] as String;
-    _chrome = await chromeLauncher.launch(url,
+    _chrome = await chromeLauncher.launch(
+      url,
       dataDir: globals.fs.currentDirectory
         .childDirectory('.dart_tool')
-        .childDirectory('chrome-device'));
+        .childDirectory('chrome-device'),
+      headless: debuggingOptions.webRunHeadless,
+      debugPort: debuggingOptions.webBrowserDebugPort,
+    );
 
     globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': true});
 
diff --git a/packages/flutter_tools/test/general.shard/web/chrome_test.dart b/packages/flutter_tools/test/general.shard/web/chrome_test.dart
index 83247b9..5bcdc94 100644
--- a/packages/flutter_tools/test/general.shard/web/chrome_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/chrome_test.dart
@@ -54,10 +54,10 @@
     resetChromeForTesting();
   });
 
-  test('can launch chrome and connect to the devtools', () => testbed.run(() async {
-    const List<String> expected = <String>[
+  List<String> expectChromeArgs({int debugPort = 1234}) {
+    return <String>[
       'example_chrome',
-      '--remote-debugging-port=1234',
+      '--remote-debugging-port=$debugPort',
       '--disable-background-timer-throttling',
       '--disable-extensions',
       '--disable-popup-blocking',
@@ -68,11 +68,18 @@
       '--disable-translate',
       'example_url',
     ];
+  }
 
+  test('can launch chrome and connect to the devtools', () => testbed.run(() async {
     await chromeLauncher.launch('example_url', skipCheck: true);
     final VerificationResult result = verify(globals.processManager.start(captureAny));
+    expect(result.captured.single, containsAll(expectChromeArgs()));
+  }));
 
-    expect(result.captured.single, containsAll(expected));
+  test('can launch chrome with a custom debug port', () => testbed.run(() async {
+    await chromeLauncher.launch('example_url', skipCheck: true, debugPort: 10000);
+    final VerificationResult result = verify(globals.processManager.start(captureAny));
+    expect(result.captured.single, containsAll(expectChromeArgs(debugPort: 10000)));
   }));
 
   test('can seed chrome temp directory with existing preferences', () => testbed.run(() async {
diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart
index 7fa4c91..d46e5d8 100644
--- a/packages/flutter_tools/test/integration.shard/test_driver.dart
+++ b/packages/flutter_tools/test/integration.shard/test_driver.dart
@@ -498,7 +498,7 @@
     // fast.
     unawaited(_process.exitCode.then((_) {
       if (!prematureExitGuard.isCompleted) {
-        prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}: $_errorBuffer');
+        prematureExitGuard.completeError('Process exited prematurely: ${args.join(' ')}: $_errorBuffer');
       }
     }));