[ermine][e2e] Add smoke test for black screen and input.

This change adds a smoke test for ermine to ensure scenic is not
drawing black screen and the input pipeline is functioning.

- It disables existing e2e tests until they are migrated to use
  flutter driver. Tracking: http://fxb/43263

Testing: New smoke test for ermine.
Change-Id: Ie315c06e35219cc8000c436b557249e32826a389
diff --git a/session_shells/ermine/login_shell/BUILD.gn b/session_shells/ermine/login_shell/BUILD.gn
index c129365..8ade5f5 100644
--- a/session_shells/ermine/login_shell/BUILD.gn
+++ b/session_shells/ermine/login_shell/BUILD.gn
@@ -7,6 +7,8 @@
 flutter_app("userpicker_base_shell") {
   main_dart = "lib/main.dart"
 
+  flutter_driver_extendable = true
+
   meta = [
     {
       path = rebase_path("meta/userpicker_base_shell.cmx")
diff --git a/session_shells/ermine/login_shell/lib/main.dart b/session_shells/ermine/login_shell/lib/main.dart
index dbf0d9d..9f4c3e0 100644
--- a/session_shells/ermine/login_shell/lib/main.dart
+++ b/session_shells/ermine/login_shell/lib/main.dart
@@ -105,7 +105,7 @@
               ? const Offstage()
               : ScopedModel<NetstackModel>(
                   model: netstackModel,
-                  child: Overlay(initialEntries: overlays),
+                  child: Overlay(key: Key('main'), initialEntries: overlays),
                 ),
     ),
   );
diff --git a/session_shells/ermine/login_shell/lib/user_list.dart b/session_shells/ermine/login_shell/lib/user_list.dart
index 919e021..5f98e63 100644
--- a/session_shells/ermine/login_shell/lib/user_list.dart
+++ b/session_shells/ermine/login_shell/lib/user_list.dart
@@ -57,12 +57,14 @@
   }
 
   Widget _buildIconButton({
+    Key key,
     VoidCallback onTap,
     bool isSmall,
     IconData icon,
   }) {
     double size = isSmall ? _kUserAvatarSizeSmall : _kUserAvatarSizeLarge;
     return _buildUserActionButton(
+      key: key,
       onTap: () => onTap?.call(),
       width: size,
       isSmall: isSmall,
@@ -77,6 +79,7 @@
   }
 
   Widget _buildUserActionButton({
+    Key key,
     Widget child,
     VoidCallback onTap,
     bool isSmall,
@@ -86,6 +89,7 @@
     return GestureDetector(
       onTap: isDisabled ? null : () => onTap?.call(),
       child: Container(
+        key: key,
         height: isSmall ? _kUserAvatarSizeSmall : _kUserAvatarSizeLarge,
         width: width ?? (isSmall ? _kButtonWidthSmall : _kButtonWidthLarge),
         alignment: FractionalOffset.center,
@@ -194,6 +198,7 @@
           isSmall: isSmall,
         ),
         _buildUserActionButton(
+          key: Key('Guest'),
           child: Text(
             'GUEST',
             style: TextStyle(
@@ -306,6 +311,7 @@
             Align(
               alignment: FractionalOffset.bottomCenter,
               child: _buildIconButton(
+                key: Key('plus'),
                 onTap: model.showUserActions,
                 isSmall: isSmall,
                 icon: Icons.add,
@@ -317,18 +323,18 @@
         children.addAll(
           model.accounts.map(
             (Account account) => Align(
-                  alignment: FractionalOffset.bottomCenter,
-                  child: _buildUserEntry(
-                    account: account,
-                    onTap: () {
-                      model
-                        ..login(account.id)
-                        ..hideUserActions();
-                    },
-                    isSmall: isSmall,
-                    model: model,
-                  ),
-                ),
+              alignment: FractionalOffset.bottomCenter,
+              child: _buildUserEntry(
+                account: account,
+                onTap: () {
+                  model
+                    ..login(account.id)
+                    ..hideUserActions();
+                },
+                isSmall: isSmall,
+                model: model,
+              ),
+            ),
           ),
         );
 
diff --git a/session_shells/ermine/shell/BUILD.gn b/session_shells/ermine/shell/BUILD.gn
index 703c257..83ae11f 100644
--- a/session_shells/ermine/shell/BUILD.gn
+++ b/session_shells/ermine/shell/BUILD.gn
@@ -23,6 +23,8 @@
   fuchsia_package_name = "ermine"
   package_name = "ermine"
 
+  flutter_driver_extendable = true
+
   meta = [
     {
       path = rebase_path("meta/ermine.cmx")
diff --git a/session_shells/ermine/shell/lib/src/widgets/status/status.dart b/session_shells/ermine/shell/lib/src/widgets/status/status.dart
index 93b746f..a8ca033 100644
--- a/session_shells/ermine/shell/lib/src/widgets/status/status.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/status/status.dart
@@ -122,7 +122,7 @@
           Padding(padding: EdgeInsets.only(right: kPadding)),
           StatusButton(Strings.shutdown, model.shutdownDevice),
           Spacer(),
-          StatusButton(Strings.logout, model.logoutSession),
+          StatusButton(Strings.logout, model.logoutSession, Key('logout')),
         ],
       ),
     );
diff --git a/session_shells/ermine/shell/lib/src/widgets/status/status_button.dart b/session_shells/ermine/shell/lib/src/widgets/status/status_button.dart
index 8a6f50b..ff71e7f 100644
--- a/session_shells/ermine/shell/lib/src/widgets/status/status_button.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/status/status_button.dart
@@ -10,7 +10,7 @@
   final String label;
   final VoidCallback onTap;
 
-  const StatusButton(this.label, this.onTap);
+  const StatusButton(this.label, this.onTap, [Key key]) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
diff --git a/tests/e2e/BUILD.gn b/tests/e2e/BUILD.gn
index b9d6983..da00f2d 100644
--- a/tests/e2e/BUILD.gn
+++ b/tests/e2e/BUILD.gn
@@ -7,13 +7,17 @@
 
 dart_test("experiences_ermine_session_shell_e2e_test") {
   sources = [
-    "ermine_session_shell_ask_test.dart",
-    "ermine_session_shell_simple_browser_test.dart",
-    "ermine_session_shell_status_test.dart",
+    "ermine_driver.dart",
+    # TODO(43263): Re-write ermine tests to use FlutterDriver.
+    # "ermine_session_shell_ask_test.dart",
+    # "ermine_session_shell_simple_browser_test.dart",
+    # "ermine_session_shell_status_test.dart",
+    "ermine_smoke_test.dart",
   ]
 
   deps = [
     "//sdk/testing/sl4f/client",
+    "//sdk/testing/sl4f/flutter_driver_sl4f",
     "//third_party/dart-pkg/pub/test",
   ]
 
diff --git a/tests/e2e/test/ermine_driver.dart b/tests/e2e/test/ermine_driver.dart
new file mode 100644
index 0000000..066c42f
--- /dev/null
+++ b/tests/e2e/test/ermine_driver.dart
@@ -0,0 +1,49 @@
+// Copyright 2019 The Fuchsia 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_driver/flutter_driver.dart';
+import 'package:flutter_driver_sl4f/flutter_driver_sl4f.dart';
+import 'package:sl4f/sl4f.dart';
+import 'package:test/test.dart';
+
+/// Defines a test utility class to drive Ermine during integration test using
+/// Flutter Driver. This utility will grow with more convenience methods in the
+/// future useful for testing.
+class ErmineDriver {
+  final Sl4f sl4f;
+  FlutterDriverConnector connector;
+
+  ErmineDriver(this.sl4f);
+
+  /// Connect to the isolate for Ermine and returns the [FlutterDriver]
+  /// instance. The instance should be closed when done.
+  Future<FlutterDriver> connect() async {
+    connector = FlutterDriverConnector(sl4f);
+    await connector.initialize();
+
+    // Check if ermine is running.
+    final isolate = await connector.isolate('ermine');
+    if (isolate == null) {
+      // Use `sessionctl` to login as guest and start ermine.
+      await sl4f.ssh.run('sessionctl restart_session');
+      final result = await sl4f.ssh.run('sessionctl login_guest');
+      if (result.exitCode != 0) {
+        fail('unable to login guest - check user already logged in?');
+      }
+    }
+
+    // Now connect to ermine.
+    final driver = await connector.driverForIsolate('ermine');
+    if (driver == null) {
+      fail('unable to connect to ermine.');
+    }
+
+    return driver;
+  }
+
+  /// Closes [FlutterDriverConnector] and performs cleanup.
+  Future<void> close() async {
+    await connector.tearDown();
+  }
+}
diff --git a/tests/e2e/test/ermine_smoke_test.dart b/tests/e2e/test/ermine_smoke_test.dart
new file mode 100644
index 0000000..e0096eb
--- /dev/null
+++ b/tests/e2e/test/ermine_smoke_test.dart
@@ -0,0 +1,98 @@
+// Copyright 2019 The Fuchsia 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 'dart:math';
+
+import 'package:flutter_driver/flutter_driver.dart';
+import 'package:flutter_driver_sl4f/flutter_driver_sl4f.dart';
+import 'package:sl4f/sl4f.dart';
+import 'package:test/test.dart';
+
+/// Tests that the DUT running ermine can do the following:
+///  - Connect to base shell (userpicker_base_shell) using Flutter Driver.
+///  - Tap login button using scenic input utility.
+///  - Connect to user shell (ermine) using Flutter Driver.
+///  - Ensure its screenshot is not all black.
+///  - Log out of user shell (ermine).
+void main() {
+  Sl4f sl4f;
+  FlutterDriverConnector connector;
+  FlutterDriver driver;
+
+  setUpAll(() async {
+    sl4f = Sl4f.fromEnvironment();
+    await sl4f.startServer();
+
+    connector = FlutterDriverConnector(sl4f);
+    await connector.initialize();
+
+    // Check if base shell is running.
+    final isolate = await connector.isolate('userpicker_base_shell');
+    if (isolate == null) {
+      fail('could not find userpicker_base_shell.');
+    }
+
+    // Now connect to base shell.
+    driver = await connector.driverForIsolate('userpicker_base_shell');
+    if (driver == null) {
+      fail('unable to connect to userpicker_base_shell.');
+    }
+  });
+
+  tearDownAll(() async {
+    await driver.close();
+    await connector.tearDown();
+    await sl4f.stopServer();
+    sl4f.close();
+  });
+
+  test('Guest login to ermine', () async {
+    // Get the center of the plus key.
+    final plusButtonFinder = find.byValueKey('plus');
+    var center = await centerFromFinder(driver, plusButtonFinder);
+
+    final input = Input(sl4f);
+    bool didTap = await input.tap(Point(center.x, center.y));
+    expect(didTap, true);
+
+    // The 'Guest' button should be visible, tap it.
+    final guestButtonFinder = find.byValueKey('Guest');
+    center = await centerFromFinder(driver, guestButtonFinder);
+
+    didTap = await input.tap(Point(center.x, center.y));
+    expect(didTap, true);
+
+    // This should launch ermine.
+    final ermineDriver = await connector.driverForIsolate('ermine');
+    if (ermineDriver == null) {
+      fail('Unable to launch ermine');
+    }
+    await ermineDriver.waitUntilNoTransientCallbacks();
+
+    // Now take a screen shot and make sure it is not all black.
+    final scenic = Scenic(sl4f);
+    final image = await scenic.takeScreenshot();
+    bool isAllBlack = image.data.every((pixel) => pixel & 0x00ffffff == 0);
+    expect(isAllBlack, false);
+
+    // Now log out of ermine.
+    final logoutFinder = find.byValueKey('logout');
+    await ermineDriver.tap(logoutFinder);
+    await ermineDriver.close();
+  });
+}
+
+Future<Point<int>> centerFromFinder(
+    FlutterDriver driver, SerializableFinder finder) async {
+  // Get the bottom right of the main screen.
+  final mainScreenFinder = find.byValueKey('main');
+  final bottomRight = await driver.getBottomRight(mainScreenFinder);
+
+  // The `input` utility expects screen coordinates to be scaled 0 - 1000.
+  final center = await driver.getCenter(finder);
+  int x = (center.dx / bottomRight.dx * 1000).toInt();
+  int y = (center.dy / bottomRight.dy * 1000).toInt();
+
+  return Point<int>(x, y);
+}