Allow signing into default profile and enabling extensions (#29)

* Allow signing into default profile and enabling extensions

- Add `signIn` argument to `startWithDebugPort` API.
- Add tests.

Related: https://github.com/dart-lang/webdev/issues/1490

* Addressed CR comments, fixed test failure

* Addressed more CR comments

* Addressed CR comments, fixed test flake

* Create tem dir for eacch test, better flake fix

* Make closing chrome on each test explicit
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66736db..8a339c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,8 @@
-## 1.0.1-dev
+## 1.1.0-dev
+
+- Add optional `signIn` argument to `startWithDebugPort`.
+  To be used together with `user-data-dir` to start a chrome
+  window signed into the default profile with extensions enabled.
 
 ## 1.0.0
 
diff --git a/lib/src/chrome.dart b/lib/src/chrome.dart
index 1391884..ed6673e 100644
--- a/lib/src/chrome.dart
+++ b/lib/src/chrome.dart
@@ -65,14 +65,25 @@
   /// Starts Chrome with the given arguments and a specific port.
   ///
   /// Each url in [urls] will be loaded in a separate tab.
+  ///
+  /// If [userDataDir] is `null`, a new temp directory will be
+  /// passed to chrome as a user data directory. Chrome will
+  /// start without sign in and with extensions disabled.
+  ///
+  /// If [userDataDir] is not `null`, it will be passed to chrome
+  /// as a user data directory. Chrome will start signed into
+  /// the default profile with extensions enabled if [signIn]
+  /// is also true.
   static Future<Chrome> startWithDebugPort(
     List<String> urls, {
     int debugPort = 0,
     bool headless = false,
     String? userDataDir,
+    bool signIn = false,
   }) async {
     Directory dataDir;
     if (userDataDir == null) {
+      signIn = false;
       dataDir = Directory.systemTemp.createTempSync();
     } else {
       dataDir = Directory(userDataDir);
@@ -87,9 +98,9 @@
       '--disable-background-timer-throttling',
       // Since we are using a temp profile, disable features that slow the
       // Chrome launch.
-      '--disable-extensions',
+      if (!signIn) '--disable-extensions',
       '--disable-popup-blocking',
-      '--bwsi',
+      if (!signIn) '--bwsi',
       '--no-first-run',
       '--no-default-browser-check',
       '--disable-default-apps',
diff --git a/pubspec.yaml b/pubspec.yaml
index 082db41..d2eb157 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
 name: browser_launcher
 description: Provides a standardized way to launch web browsers for testing and tools.
 
-version: 1.0.1-dev
+version: 1.1.0-dev
 
 homepage: https://github.com/dart-lang/browser_launcher
 
diff --git a/test/chrome_test.dart b/test/chrome_test.dart
index 0263225..0c098a2 100644
--- a/test/chrome_test.dart
+++ b/test/chrome_test.dart
@@ -4,6 +4,7 @@
 
 @OnPlatform({'windows': Skip('appveyor is not setup to install Chrome')})
 import 'dart:async';
+import 'dart:io';
 
 import 'package:browser_launcher/src/chrome.dart';
 import 'package:test/test.dart';
@@ -12,48 +13,137 @@
 void main() {
   Chrome? chrome;
 
-  Future<void> launchChromeWithDebugPort({int port = 0}) async {
-    chrome = await Chrome.startWithDebugPort([_googleUrl], debugPort: port);
+  Future<WipConnection> connectToTab(String url) async {
+    var tab = await chrome!.chromeConnection.getTab((t) => t.url.contains(url));
+    expect(tab, isNotNull);
+    return tab!.connect();
+  }
+
+  Future<void> openTab(String url) async {
+    await chrome!.chromeConnection.getUrl(_openTabUrl(url));
+  }
+
+  Future<void> launchChromeWithDebugPort(
+      {int port = 0, String? userDataDir, bool signIn = false}) async {
+    chrome = await Chrome.startWithDebugPort([_googleUrl],
+        debugPort: port, userDataDir: userDataDir, signIn: signIn);
   }
 
   Future<void> launchChrome() async {
     await Chrome.start([_googleUrl]);
   }
 
-  tearDown(() async {
-    await chrome?.close();
-    chrome = null;
+  group('chrome with temp data dir', () {
+    tearDown(() async {
+      await chrome?.close();
+      chrome = null;
+    });
+
+    test('can launch chrome', () async {
+      await launchChrome();
+      expect(chrome, isNull);
+    });
+
+    test('can launch chrome with debug port', () async {
+      await launchChromeWithDebugPort();
+      expect(chrome, isNotNull);
+    });
+
+    test('has a working debugger', () async {
+      await launchChromeWithDebugPort();
+      var tabs = await chrome!.chromeConnection.getTabs();
+      expect(
+          tabs,
+          contains(const TypeMatcher<ChromeTab>()
+              .having((t) => t.url, 'url', _googleUrl)));
+    });
+
+    test('uses open debug port if provided port is 0', () async {
+      await launchChromeWithDebugPort(port: 0);
+      expect(chrome!.debugPort, isNot(equals(0)));
+    });
+
+    test('can provide a specific debug port', () async {
+      var port = await findUnusedPort();
+      await launchChromeWithDebugPort(port: port);
+      expect(chrome!.debugPort, port);
+    });
   });
 
-  test('can launch chrome', () async {
-    await launchChrome();
-    expect(chrome, isNull);
-  });
+  group('chrome with user data dir', () {
+    late Directory dataDir;
 
-  test('can launch chrome with debug port', () async {
-    await launchChromeWithDebugPort();
-    expect(chrome, isNotNull);
-  });
+    for (var signIn in [false, true]) {
+      group('and signIn = $signIn', () {
+        setUp(() {
+          dataDir = Directory.systemTemp.createTempSync(_userDataDirName);
+        });
 
-  test('debugger is working', () async {
-    await launchChromeWithDebugPort();
-    var tabs = await chrome!.chromeConnection.getTabs();
-    expect(
-        tabs,
-        contains(const TypeMatcher<ChromeTab>()
-            .having((t) => t.url, 'url', _googleUrl)));
-  });
+        tearDown(() async {
+          await chrome?.close();
+          chrome = null;
 
-  test('uses open debug port if provided port is 0', () async {
-    await launchChromeWithDebugPort(port: 0);
-    expect(chrome!.debugPort, isNot(equals(0)));
-  });
+          var attempts = 0;
+          while (true) {
+            try {
+              attempts++;
+              await Future.delayed(const Duration(milliseconds: 100));
+              dataDir.deleteSync(recursive: true);
+              break;
+            } catch (_) {
+              if (attempts > 3) rethrow;
+            }
+          }
+        });
 
-  test('can provide a specific debug port', () async {
-    var port = await findUnusedPort();
-    await launchChromeWithDebugPort(port: port);
-    expect(chrome!.debugPort, port);
+        test('can launch with debug port', () async {
+          await launchChromeWithDebugPort(
+              userDataDir: dataDir.path, signIn: signIn);
+          expect(chrome, isNotNull);
+        });
+
+        test('has a working debugger', () async {
+          await launchChromeWithDebugPort(
+              userDataDir: dataDir.path, signIn: signIn);
+          var tabs = await chrome!.chromeConnection.getTabs();
+          expect(
+              tabs,
+              contains(const TypeMatcher<ChromeTab>()
+                  .having((t) => t.url, 'url', _googleUrl)));
+        });
+
+        test('has correct profile path', () async {
+          await launchChromeWithDebugPort(
+              userDataDir: dataDir.path, signIn: signIn);
+          await openTab(_chromeVersionUrl);
+
+          var wipConnection = await connectToTab(_chromeVersionUrl);
+          var result = await _evaluateExpression(wipConnection.page,
+              "document.getElementById('profile_path').textContent");
+
+          expect(result, contains(_userDataDirName));
+        });
+      });
+    }
   });
 }
 
+String _openTabUrl(String url) => '/json/new?$url';
+
+Future<String> _evaluateExpression(WipPage page, String expression) async {
+  var result = '';
+  while (result.isEmpty) {
+    await Future.delayed(Duration(milliseconds: 100));
+    var wipResponse = await page.sendCommand(
+      'Runtime.evaluate',
+      params: {'expression': expression},
+    );
+    var value = wipResponse.json['result']['result']['value'];
+    result = (value != null && value is String) ? value : '';
+  }
+  return result;
+}
+
 const _googleUrl = 'https://www.google.com/';
+const _chromeVersionUrl = 'chrome://version/';
+const _userDataDirName = 'data dir';