[google_sign_in_web] Ensure plugin throws PlatformExceptions (#2943)

Instead of throwing JS objects, or attempting to access undefined properties.

This change also migrates tests to the `integration_test` package.
diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
index 35a6934..d71badc 100644
--- a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
+++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 0.9.2
+
+* Throw PlatformExceptions from where the GMaps SDK may throw exceptions: `init()` and `signIn()`.
+* Add two new JS-interop types to be able to unwrap JS errors in release mode.
+* Align the fields of the thrown PlatformExceptions with the mobile version.
+* Migrate tests to run with `flutter drive`
+
 ## 0.9.1+2
 
 * Update package:e2e reference to use the local version in the flutter/plugins
diff --git a/packages/google_sign_in/google_sign_in_web/README.md b/packages/google_sign_in/google_sign_in_web/README.md
index 930212c..7a44e99 100644
--- a/packages/google_sign_in/google_sign_in_web/README.md
+++ b/packages/google_sign_in/google_sign_in_web/README.md
@@ -98,11 +98,7 @@
 
 Tests are a crucial to contributions to this package. All new contributions should be reasonably tested.
 
-In order to run tests in this package, do:
-
-```
-flutter test --platform chrome -j1
-```
+**Check the [`test/README.md` file](https://github.com/flutter/plugins/blob/master/packages/google_sign_in/google_sign_in_web/test/README.md)** for more information on how to run tests on this package.
 
 Contributions to this package are welcome. Read the [Contributing to Flutter Plugins](https://github.com/flutter/plugins/blob/master/CONTRIBUTING.md) guide to get started.
 
diff --git a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart
index bb43ba1..dd82852 100644
--- a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart
+++ b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart
@@ -105,16 +105,17 @@
       // state of the authentication, i.e: if you logout elsewhere...
 
       isAuthInitialized.complete();
-    }), allowInterop((dynamic reason) {
+    }), allowInterop((auth2.GoogleAuthInitFailureError reason) {
       // onError
-      throw PlatformException(
-        code: 'google_sign_in',
-        message: reason.error,
-        details: reason.details,
-      );
+      isAuthInitialized.completeError(PlatformException(
+        code: reason.error,
+        message: reason.details,
+        details:
+            'https://developers.google.com/identity/sign-in/web/reference#error_codes',
+      ));
     }));
 
-    return null;
+    return _isAuthInitialized;
   }
 
   @override
@@ -128,8 +129,16 @@
   @override
   Future<GoogleSignInUserData> signIn() async {
     await initialized;
-
-    return gapiUserToPluginUserData(await auth2.getAuthInstance().signIn());
+    try {
+      return gapiUserToPluginUserData(await auth2.getAuthInstance().signIn());
+    } on auth2.GoogleAuthSignInError catch (reason) {
+      throw PlatformException(
+        code: reason.error,
+        message: 'Exception raised from GoogleAuth.signIn()',
+        details:
+            'https://developers.google.com/identity/sign-in/web/reference#error_codes_2',
+      );
+    }
   }
 
   @override
diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart
index e05bedf..ed7a281 100644
--- a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart
+++ b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart
@@ -16,6 +16,23 @@
 
 /// <reference types="gapi" />
 
+@anonymous
+@JS()
+class GoogleAuthInitFailureError {
+  external String get error;
+  external set error(String value);
+
+  external String get details;
+  external set details(String value);
+}
+
+@anonymous
+@JS()
+class GoogleAuthSignInError {
+  external String get error;
+  external set error(String value);
+}
+
 // Module gapi.auth2
 /// GoogleAuth is a singleton class that provides methods to allow the user to sign in with a Google account,
 /// get the user's current sign-in status, get specific data from the user's Google profile,
@@ -30,7 +47,7 @@
   /// Calls the onInit function when the GoogleAuth object is fully initialized, or calls the onFailure function if
   /// initialization fails.
   external dynamic then(dynamic onInit(GoogleAuth googleAuth),
-      [dynamic onFailure(dynamic /*{error: string, details: string}*/ reason)]);
+      [dynamic onFailure(GoogleAuthInitFailureError reason)]);
 
   /// Signs out all accounts from the application.
   external dynamic signOut();
diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml
index 8b48d36..70758ac 100644
--- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Flutter plugin for Google Sign-In, a secure authentication system
   for signing in with a Google account on Android, iOS and Web.
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_web
-version: 0.9.1+2
+version: 0.9.2
 
 flutter:
   plugin:
@@ -26,6 +26,8 @@
   google_sign_in: ^4.0.14
   pedantic: ^1.8.0
   mockito: ^4.1.1
+  integration_test:
+    path: ../../integration_test
 
 environment:
   sdk: ">=2.6.0 <3.0.0"
diff --git a/packages/google_sign_in/google_sign_in_web/test/README.md b/packages/google_sign_in/google_sign_in_web/test/README.md
new file mode 100644
index 0000000..7c48d02
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/README.md
@@ -0,0 +1,17 @@
+# Running browser_tests
+
+Make sure you have updated to the latest Flutter master.
+
+1. Check what version of Chrome is running on the machine you're running tests on.
+
+2. Download and install driver for that version from here:
+    * <https://chromedriver.chromium.org/downloads>
+
+3. Start the driver using `chromedriver --port=4444`
+
+4. Change into the `test` directory of your clone.
+
+5. Run tests: `flutter drive -d web-server --browser-name=chrome --target=test_driver/TEST_NAME_integration.dart`, or (in Linux):
+
+    * Single: `./run_test test_driver/TEST_NAME_integration.dart`
+    * All: `./run_test`
diff --git a/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart b/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart
deleted file mode 100644
index 40bc8a4..0000000
--- a/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-@TestOn('browser')
-
-import 'package:flutter_test/flutter_test.dart';
-import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
-import 'package:google_sign_in_web/google_sign_in_web.dart';
-import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks;
-import 'utils.dart';
-
-void main() {
-  GoogleSignInTokenData expectedTokenData =
-      GoogleSignInTokenData(idToken: '70k3n', accessToken: 'access_70k3n');
-
-  GoogleSignInUserData expectedUserData = GoogleSignInUserData(
-    displayName: 'Foo Bar',
-    email: 'foo@example.com',
-    id: '123',
-    photoUrl: 'http://example.com/img.jpg',
-    idToken: expectedTokenData.idToken,
-  );
-
-  // The pre-configured use case for the instances of the plugin in this test
-  gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(expectedUserData));
-
-  GoogleSignInPlugin plugin;
-
-  setUp(() {
-    plugin = GoogleSignInPlugin();
-  });
-
-  test('Init requires clientId', () async {
-    expect(plugin.init(hostedDomain: ''), throwsAssertionError);
-  });
-
-  test('Init doesn\'t accept spaces in scopes', () async {
-    expect(
-        plugin.init(
-          hostedDomain: '',
-          clientId: '',
-          scopes: <String>['scope with spaces'],
-        ),
-        throwsAssertionError);
-  });
-
-  group('Successful .init, then', () {
-    setUp(() async {
-      await plugin.init(
-        hostedDomain: 'foo',
-        scopes: <String>['some', 'scope'],
-        clientId: '1234',
-      );
-      await plugin.initialized;
-    });
-
-    test('signInSilently', () async {
-      GoogleSignInUserData actualUser = await plugin.signInSilently();
-
-      expect(actualUser, expectedUserData);
-    });
-
-    test('signIn', () async {
-      GoogleSignInUserData actualUser = await plugin.signIn();
-
-      expect(actualUser, expectedUserData);
-    });
-
-    test('getTokens', () async {
-      GoogleSignInTokenData actualToken =
-          await plugin.getTokens(email: expectedUserData.email);
-
-      expect(actualToken, expectedTokenData);
-    });
-
-    test('requestScopes', () async {
-      bool scopeGranted = await plugin.requestScopes(['newScope']);
-
-      expect(scopeGranted, isTrue);
-    });
-  });
-}
diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart
deleted file mode 100644
index 1846033..0000000
--- a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-part of gapi_mocks;
-
-// JS mock of a gapi.auth2, with a successfully identified user
-String auth2InitSuccess(GoogleSignInUserData userData) => testIife('''
-${gapi()}
-
-var mockUser = ${googleUser(userData)};
-
-function GapiAuth2() {}
-GapiAuth2.prototype.init = function (initOptions) {
-  return {
-    then: (onSuccess, onError) => {
-      window.setTimeout(() => {
-        onSuccess(window.gapi.auth2);
-      }, 30);
-    },
-    currentUser: {
-      listen: (cb) => {
-        window.setTimeout(() => {
-          cb(mockUser);
-        }, 30);
-      }
-    }
-  }
-};
-
-GapiAuth2.prototype.getAuthInstance = function () {
-  return {
-    signIn: () => {
-      return new Promise((resolve, reject) => {
-        window.setTimeout(() => {
-          resolve(mockUser);
-        }, 30);
-      });
-    },
-    currentUser: {
-      get: () => mockUser,
-    },
-  }
-};
-
-window.gapi.auth2 = new GapiAuth2();
-''');
diff --git a/packages/google_sign_in/google_sign_in_web/test/lib/main.dart b/packages/google_sign_in/google_sign_in_web/test/lib/main.dart
new file mode 100644
index 0000000..1041520
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/lib/main.dart
@@ -0,0 +1,22 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+
+void main() {
+  runApp(MyApp());
+}
+
+/// App for testing
+class MyApp extends StatefulWidget {
+  @override
+  _MyAppState createState() => _MyAppState();
+}
+
+class _MyAppState extends State<MyApp> {
+  @override
+  Widget build(BuildContext context) {
+    return Text('Testing... Look at the console output for results!');
+  }
+}
diff --git a/packages/google_sign_in/google_sign_in_web/test/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/test/pubspec.yaml
new file mode 100644
index 0000000..dd0354e
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/pubspec.yaml
@@ -0,0 +1,24 @@
+name: regular_integration_tests
+publish_to: none
+
+environment:
+  sdk: ">=2.2.2 <3.0.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+
+dev_dependencies:
+  google_sign_in: ^4.5.3
+  flutter_driver:
+    sdk: flutter
+  flutter_test:
+    sdk: flutter
+  http: ^0.12.2
+  mockito: ^4.1.1
+  integration_test:
+    path: ../../../integration_test
+
+dependency_overrides:
+  google_sign_in_web:
+    path: ../
diff --git a/packages/google_sign_in/google_sign_in_web/test/run_test b/packages/google_sign_in/google_sign_in_web/test/run_test
new file mode 100755
index 0000000..74a8526
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/run_test
@@ -0,0 +1,17 @@
+#!/usr/bin/bash
+if pgrep -lf chromedriver > /dev/null; then
+  echo "chromedriver is running."
+
+  if [ $# -eq 0 ]; then
+    echo "No target specified, running all tests..."
+    find test_driver/ -iname *_integration.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --target='{}'
+  else
+    echo "Running test target: $1..."
+    set -x
+    flutter drive -d web-server --web-port=7357 --browser-name=chrome --target=$1
+  fi
+
+  else
+    echo "chromedriver is not running."
+fi
+
diff --git a/packages/google_sign_in/google_sign_in_web/test/test_driver/auth2_integration.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/auth2_integration.dart
new file mode 100644
index 0000000..e2f16f2
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/test_driver/auth2_integration.dart
@@ -0,0 +1,192 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
+import 'package:google_sign_in_web/google_sign_in_web.dart';
+import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks;
+import 'src/test_utils.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  GoogleSignInTokenData expectedTokenData =
+      GoogleSignInTokenData(idToken: '70k3n', accessToken: 'access_70k3n');
+
+  GoogleSignInUserData expectedUserData = GoogleSignInUserData(
+    displayName: 'Foo Bar',
+    email: 'foo@example.com',
+    id: '123',
+    photoUrl: 'http://example.com/img.jpg',
+    idToken: expectedTokenData.idToken,
+  );
+
+  GoogleSignInPlugin plugin;
+
+  group('plugin.init() throws a catchable exception', () {
+    setUp(() {
+      // The pre-configured use case for the instances of the plugin in this test
+      gapiUrl = toBase64Url(gapi_mocks.auth2InitError());
+      plugin = GoogleSignInPlugin();
+    });
+
+    testWidgets('init throws PlatformException', (WidgetTester tester) async {
+      await expectLater(
+          plugin.init(
+            hostedDomain: 'foo',
+            scopes: <String>['some', 'scope'],
+            clientId: '1234',
+          ),
+          throwsA(isA<PlatformException>()));
+    });
+
+    testWidgets('init forwards error code from JS',
+        (WidgetTester tester) async {
+      try {
+        await plugin.init(
+          hostedDomain: 'foo',
+          scopes: <String>['some', 'scope'],
+          clientId: '1234',
+        );
+        fail('plugin.init should have thrown an exception!');
+      } catch (e) {
+        expect(e.code, 'idpiframe_initialization_failed');
+      }
+    });
+  });
+
+  group('other methods also throw catchable exceptions on init fail', () {
+    // This function ensures that init gets called, but for some reason, we
+    // ignored that it has thrown stuff...
+    void _discardInit() async {
+      try {
+        await plugin.init(
+          hostedDomain: 'foo',
+          scopes: <String>['some', 'scope'],
+          clientId: '1234',
+        );
+      } catch (e) {
+        // Noop so we can call other stuff
+      }
+    }
+
+    setUp(() {
+      gapiUrl = toBase64Url(gapi_mocks.auth2InitError());
+      plugin = GoogleSignInPlugin();
+    });
+
+    testWidgets('signInSilently throws', (WidgetTester tester) async {
+      await _discardInit();
+      await expectLater(
+          plugin.signInSilently(), throwsA(isA<PlatformException>()));
+    });
+
+    testWidgets('signIn throws', (WidgetTester tester) async {
+      await _discardInit();
+      await expectLater(plugin.signIn(), throwsA(isA<PlatformException>()));
+    });
+
+    testWidgets('getTokens throws', (WidgetTester tester) async {
+      await _discardInit();
+      await expectLater(plugin.getTokens(email: 'test@example.com'),
+          throwsA(isA<PlatformException>()));
+    });
+    testWidgets('requestScopes', (WidgetTester tester) async {
+      await _discardInit();
+      await expectLater(plugin.requestScopes(['newScope']),
+          throwsA(isA<PlatformException>()));
+    });
+  });
+
+  group('auth2 Init Successful', () {
+    setUp(() {
+      // The pre-configured use case for the instances of the plugin in this test
+      gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(expectedUserData));
+      plugin = GoogleSignInPlugin();
+    });
+
+    testWidgets('Init requires clientId', (WidgetTester tester) async {
+      expect(plugin.init(hostedDomain: ''), throwsAssertionError);
+    });
+
+    testWidgets('Init doesn\'t accept spaces in scopes',
+        (WidgetTester tester) async {
+      expect(
+          plugin.init(
+            hostedDomain: '',
+            clientId: '',
+            scopes: <String>['scope with spaces'],
+          ),
+          throwsAssertionError);
+    });
+
+    group('Successful .init, then', () {
+      setUp(() async {
+        await plugin.init(
+          hostedDomain: 'foo',
+          scopes: <String>['some', 'scope'],
+          clientId: '1234',
+        );
+        await plugin.initialized;
+      });
+
+      testWidgets('signInSilently', (WidgetTester tester) async {
+        GoogleSignInUserData actualUser = await plugin.signInSilently();
+
+        expect(actualUser, expectedUserData);
+      });
+
+      testWidgets('signIn', (WidgetTester tester) async {
+        GoogleSignInUserData actualUser = await plugin.signIn();
+
+        expect(actualUser, expectedUserData);
+      });
+
+      testWidgets('getTokens', (WidgetTester tester) async {
+        GoogleSignInTokenData actualToken =
+            await plugin.getTokens(email: expectedUserData.email);
+
+        expect(actualToken, expectedTokenData);
+      });
+
+      testWidgets('requestScopes', (WidgetTester tester) async {
+        bool scopeGranted = await plugin.requestScopes(['newScope']);
+
+        expect(scopeGranted, isTrue);
+      });
+    });
+  });
+
+  group('auth2 Init successful, but exception on signIn() method', () {
+    setUp(() async {
+      // The pre-configured use case for the instances of the plugin in this test
+      gapiUrl = toBase64Url(gapi_mocks.auth2SignInError());
+      plugin = GoogleSignInPlugin();
+      await plugin.init(
+        hostedDomain: 'foo',
+        scopes: <String>['some', 'scope'],
+        clientId: '1234',
+      );
+      await plugin.initialized;
+    });
+
+    testWidgets('User aborts sign in flow, throws PlatformException',
+        (WidgetTester tester) async {
+      await expectLater(plugin.signIn(), throwsA(isA<PlatformException>()));
+    });
+
+    testWidgets('User aborts sign in flow, error code is forwarded from JS',
+        (WidgetTester tester) async {
+      try {
+        await plugin.signIn();
+        fail('plugin.signIn() should have thrown an exception!');
+      } catch (e) {
+        expect(e.code, 'popup_closed_by_user');
+      }
+    });
+  });
+}
diff --git a/packages/google_sign_in/google_sign_in_web/test/test_driver/auth2_integration_test.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/auth2_integration_test.dart
new file mode 100644
index 0000000..39444c0
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/test_driver/auth2_integration_test.dart
@@ -0,0 +1,7 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:integration_test/integration_test_driver.dart';
+
+Future<void> main() async => integrationDriver();
diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_load_integration.dart
similarity index 82%
rename from packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart
rename to packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_load_integration.dart
index 6703bec..540369c 100644
--- a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart
+++ b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_load_integration.dart
@@ -2,21 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-@TestOn('browser')
-
 import 'dart:html' as html;
 
+import 'package:integration_test/integration_test.dart';
+
 import 'package:flutter_test/flutter_test.dart';
 import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
 import 'package:google_sign_in_web/google_sign_in_web.dart';
 import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks;
-import 'utils.dart';
+import 'src/test_utils.dart';
 
 void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
   gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(GoogleSignInUserData()));
 
-  test('Plugin is initialized after GAPI fully loads and init is called',
-      () async {
+  testWidgets('Plugin is initialized after GAPI fully loads and init is called',
+      (WidgetTester tester) async {
     expect(
       html.querySelector('script[src^="data:"]'),
       isNull,
diff --git a/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_load_integration_test.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_load_integration_test.dart
new file mode 100644
index 0000000..39444c0
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_load_integration_test.dart
@@ -0,0 +1,7 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:integration_test/integration_test_driver.dart';
+
+Future<void> main() async => integrationDriver();
diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/gapi_mocks.dart
similarity index 100%
rename from packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart
rename to packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/gapi_mocks.dart
diff --git a/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/src/auth2_init.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/src/auth2_init.dart
new file mode 100644
index 0000000..79d798a
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/src/auth2_init.dart
@@ -0,0 +1,107 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of gapi_mocks;
+
+// JS mock of a gapi.auth2, with a successfully identified user
+String auth2InitSuccess(GoogleSignInUserData userData) => testIife('''
+${gapi()}
+
+var mockUser = ${googleUser(userData)};
+
+function GapiAuth2() {}
+GapiAuth2.prototype.init = function (initOptions) {
+  return {
+    then: (onSuccess, onError) => {
+      window.setTimeout(() => {
+        onSuccess(window.gapi.auth2);
+      }, 30);
+    },
+    currentUser: {
+      listen: (cb) => {
+        window.setTimeout(() => {
+          cb(mockUser);
+        }, 30);
+      }
+    }
+  }
+};
+
+GapiAuth2.prototype.getAuthInstance = function () {
+  return {
+    signIn: () => {
+      return new Promise((resolve, reject) => {
+        window.setTimeout(() => {
+          resolve(mockUser);
+        }, 30);
+      });
+    },
+    currentUser: {
+      get: () => mockUser,
+    },
+  }
+};
+
+window.gapi.auth2 = new GapiAuth2();
+''');
+
+String auth2InitError() => testIife('''
+${gapi()}
+
+function GapiAuth2() {}
+GapiAuth2.prototype.init = function (initOptions) {
+  return {
+    then: (onSuccess, onError) => {
+      window.setTimeout(() => {
+        onError({
+          error: 'idpiframe_initialization_failed',
+          details: 'This error was raised from a test.',
+        });
+      }, 30);
+    }
+  }
+};
+
+window.gapi.auth2 = new GapiAuth2();
+''');
+
+String auth2SignInError([String error = 'popup_closed_by_user']) => testIife('''
+${gapi()}
+
+var mockUser = null;
+
+function GapiAuth2() {}
+GapiAuth2.prototype.init = function (initOptions) {
+  return {
+    then: (onSuccess, onError) => {
+      window.setTimeout(() => {
+        onSuccess(window.gapi.auth2);
+      }, 30);
+    },
+    currentUser: {
+      listen: (cb) => {
+        window.setTimeout(() => {
+          cb(mockUser);
+        }, 30);
+      }
+    }
+  }
+};
+
+GapiAuth2.prototype.getAuthInstance = function () {
+  return {
+    signIn: () => {
+      return new Promise((resolve, reject) => {
+        window.setTimeout(() => {
+          reject({
+            error: '${error}'
+          });
+        }, 30);
+      });
+    },
+  }
+};
+
+window.gapi.auth2 = new GapiAuth2();
+''');
diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/src/gapi.dart
similarity index 100%
rename from packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart
rename to packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/src/gapi.dart
diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/src/google_user.dart
similarity index 100%
rename from packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart
rename to packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/src/google_user.dart
diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/src/test_iife.dart
similarity index 100%
rename from packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart
rename to packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_mocks/src/test_iife.dart
diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_utils_test.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_utils_integration.dart
similarity index 70%
rename from packages/google_sign_in/google_sign_in_web/test/gapi_utils_test.dart
rename to packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_utils_integration.dart
index 2dc49fc..55b9428 100644
--- a/packages/google_sign_in/google_sign_in_web/test/gapi_utils_test.dart
+++ b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_utils_integration.dart
@@ -1,10 +1,10 @@
 // Copyright 2019 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-@TestOn('browser')
-
 import 'package:flutter_test/flutter_test.dart';
 
+import 'package:integration_test/integration_test.dart';
+
 import 'package:google_sign_in_web/src/generated/gapiauth2.dart' as gapi;
 import 'package:google_sign_in_web/src/utils.dart';
 import 'package:mockito/mockito.dart';
@@ -15,6 +15,7 @@
 
 void main() {
   // The non-null use cases are covered by the auth2_test.dart file.
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 
   group('gapiUserToPluginUserData', () {
     var mockUser;
@@ -23,21 +24,24 @@
       mockUser = MockGoogleUser();
     });
 
-    test('null user -> null response', () {
+    testWidgets('null user -> null response', (WidgetTester tester) async {
       expect(gapiUserToPluginUserData(null), isNull);
     });
 
-    test('not signed-in user -> null response', () {
+    testWidgets('not signed-in user -> null response',
+        (WidgetTester tester) async {
       when(mockUser.isSignedIn()).thenReturn(false);
       expect(gapiUserToPluginUserData(mockUser), isNull);
     });
 
-    test('signed-in, but null profile user -> null response', () {
+    testWidgets('signed-in, but null profile user -> null response',
+        (WidgetTester tester) async {
       when(mockUser.isSignedIn()).thenReturn(true);
       expect(gapiUserToPluginUserData(mockUser), isNull);
     });
 
-    test('signed-in, null userId in profile user -> null response', () {
+    testWidgets('signed-in, null userId in profile user -> null response',
+        (WidgetTester tester) async {
       when(mockUser.isSignedIn()).thenReturn(true);
       when(mockUser.getBasicProfile()).thenReturn(MockBasicProfile());
       expect(gapiUserToPluginUserData(mockUser), isNull);
diff --git a/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_utils_integration_test.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_utils_integration_test.dart
new file mode 100644
index 0000000..39444c0
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/test_driver/gapi_utils_integration_test.dart
@@ -0,0 +1,7 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:integration_test/integration_test_driver.dart';
+
+Future<void> main() async => integrationDriver();
diff --git a/packages/google_sign_in/google_sign_in_web/test/utils.dart b/packages/google_sign_in/google_sign_in_web/test/test_driver/src/test_utils.dart
similarity index 100%
rename from packages/google_sign_in/google_sign_in_web/test/utils.dart
rename to packages/google_sign_in/google_sign_in_web/test/test_driver/src/test_utils.dart
diff --git a/packages/google_sign_in/google_sign_in_web/test/web/index.html b/packages/google_sign_in/google_sign_in_web/test/web/index.html
new file mode 100644
index 0000000..59a832b
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/test/web/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<!-- Copyright 2014 The Flutter Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+<html>
+    <head>
+        <title>Browser Tests</title>
+    </head>
+    <body>
+        <script src="main.dart.js"></script>
+    </body>
+</html>
+