[e2e] add e2e test for ermine session shell

This test verifies that the session shell can successfully
login a guest user (unauthenticated) and use the ask bar.

fx set workstation.chromebook-x64
fx build/pave
./topaz/shell/ermine/test/run-e2e-ermine

Bug: DNO-565
Change-Id: Iad8caa90bf06e2ca493b9c57a950a48fdbf44e04
diff --git a/packages/tests/BUILD.gn b/packages/tests/BUILD.gn
index 337d944..924f956 100644
--- a/packages/tests/BUILD.gn
+++ b/packages/tests/BUILD.gn
@@ -204,3 +204,10 @@
     "//topaz/tests/dart_fidl_benchmarks",
   ]
 }
+
+group("end_to_end") {
+  testonly = true
+  public_deps = [
+    "//topaz/shell/ermine/test/end_to_end:test",
+    ]
+}
diff --git a/shell/ermine/test/end_to_end/BUILD.gn b/shell/ermine/test/end_to_end/BUILD.gn
new file mode 100644
index 0000000..f9c1fd8
--- /dev/null
+++ b/shell/ermine/test/end_to_end/BUILD.gn
@@ -0,0 +1,31 @@
+# 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("//build/dart/test.gni")
+import("//build/testing/environments.gni")
+
+dart_test("ermine_session_shell_e2e_test") {
+  sources = [
+    "ermine_session_shell_test.dart",
+  ]
+
+  deps = [
+    "//sdk/testing/sl4f/client",
+    "//third_party/dart-pkg/pub/test",
+  ]
+
+  args = []
+
+  if (is_debug) {
+    args += [ "--debug" ]
+  }
+}
+
+group("test") {
+  testonly = true
+
+  deps = [
+    ":ermine_session_shell_e2e_test($host_toolchain)",
+  ]
+}
diff --git a/shell/ermine/test/end_to_end/analysis_options.yaml b/shell/ermine/test/end_to_end/analysis_options.yaml
new file mode 100644
index 0000000..03a7017
--- /dev/null
+++ b/shell/ermine/test/end_to_end/analysis_options.yaml
@@ -0,0 +1,5 @@
+# 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.
+
+include: ../../../../tools/analysis_options.yaml
diff --git a/shell/ermine/test/end_to_end/pubspec.yaml b/shell/ermine/test/end_to_end/pubspec.yaml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/shell/ermine/test/end_to_end/pubspec.yaml
diff --git a/shell/ermine/test/end_to_end/test/ermine_session_shell_test.dart b/shell/ermine/test/end_to_end/test/ermine_session_shell_test.dart
new file mode 100644
index 0000000..8315575
--- /dev/null
+++ b/shell/ermine/test/end_to_end/test/ermine_session_shell_test.dart
@@ -0,0 +1,126 @@
+// 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:convert';
+
+import 'package:sl4f/sl4f.dart';
+import 'package:test/test.dart';
+import 'package:logging/logging.dart';
+
+final _log = Logger('ermine_e2e_test');
+
+/// Tests that the DUT running ermine can do the following:
+///  - login as guest (unauthenticated login)
+///  - show the ask_bar
+///  - execute commands via the ask_bar
+void main() {
+
+  Sl4f sl4fDriver;
+
+  setUp(() async {
+    sl4fDriver = Sl4f.fromEnvironment();
+    await sl4fDriver.startServer();
+  });
+
+  tearDown(() async {
+    await sl4fDriver.stopServer();
+    sl4fDriver.close();
+  });
+
+  test('ermine session shell guest login', () async {
+
+    // TODO: note that 'user picker' screen is changing/going away
+    // so here we use sessionctl to do guest login. Once final
+    // OOBE UI is established for Ermine, this could be revisited
+    // so as to provide for more robust testing of no-auth login.
+    bool loggedIn = await sl4fDriver.ssh('sessionctl login_guest');
+
+    if (!loggedIn) {
+      fail('unable to login guest - check user already logged in?');
+    }
+
+    // allow time for shell to startup and ask bar to show
+    await Future.delayed(Duration(seconds: 5));
+
+    // verify that ask bar is active
+    bool askBarActive = await _checkActiveComponent(
+      driver:sl4fDriver, name:'ermine_ask_module');
+
+    if (!askBarActive) {
+      fail('ask bar is not active');
+    }
+
+    // Ask bar should have focus at this point, so we should
+    // be able to inject text entry via the shell and have it
+    // enter the bar.
+
+    // Note sl4f library 'input' support doesn't currently support
+    // text or keyevents. In addition to that, input keyevent doesn't
+    // appear to support keyevents with modifiers (otherwise we could
+    // tie into the show/hide of the askbar and test with esc/ALT+space)
+
+    // As it is, here we'll just open simple browser so that we can
+    // verify launching chromium from the ask bar
+    await sl4fDriver.ssh('input text simple_browser');
+
+    // Have to drive ask bar from suggestion (can't just input
+    // 'simple_browser' text and enter). So give the suggestions
+    // a second to settle down.
+    await Future.delayed(Duration(seconds: 5));
+
+    // with the top suggestion being 'open simple_browser', we can
+    // now just inject 'enter' to trigger the action.
+    await sl4fDriver.ssh('input keyevent 40');
+
+    // allow time for simple_browser to startup
+    await Future.delayed(Duration(seconds: 10));
+
+    // verify that simple_browser is active
+    bool browserActive = await _checkActiveComponent(
+      driver:sl4fDriver, name:'simple_browser');
+
+    if (!browserActive) {
+      fail('simple_browser is not active');
+    }
+
+    return;
+  });
+}
+
+/// checks to see if the component associated with [name] is running.
+Future<bool> _checkActiveComponent({Sl4f driver, String name}) async {
+
+  Future<String> getComponents() async {
+    // Note: using 'cs' here as iquery doesn't appear to actually provide a
+    // list of these active components via the hub.
+    final process = await driver.sshProcess('cs');
+    final exitCode = await process.exitCode;
+    final stdout = await process.stdout.transform(utf8.decoder).join();
+    if (exitCode != 0) {
+      final stderr = await process.stderr.transform(utf8.decoder).join();
+      _log.severe('cs failed with exit code $exitCode');
+      if (stdout.isNotEmpty) {
+        _log.severe('cs stdout: $stdout');
+      }
+      if (stderr.isNotEmpty) {
+        _log.severe('cs stderr: $stderr');
+      }
+      return null;
+    }
+    return stdout;
+  }
+
+  final activeComponents = await getComponents();
+
+  if (activeComponents == null) {
+    return false;
+  }
+
+  final matchingActiveComponentList = activeComponents.split('\n').where((line) {
+    return line.contains(name);
+  }).toList();
+
+  return matchingActiveComponentList.isNotEmpty;
+}
+
diff --git a/shell/ermine/test/run-e2e-ermine b/shell/ermine/test/run-e2e-ermine
new file mode 100755
index 0000000..59c082e
--- /dev/null
+++ b/shell/ermine/test/run-e2e-ermine
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+### run e2e tests for ermine shell
+## Usage: ./topaz/shell/ermine/test/run-e2e-ermine
+##
+## Runs the End to End tests from the current build, against the current
+## hardware target.
+##
+## Hint:
+## Use fx --dir and/or fx -d flags to modulate the selected build directory or
+## target device.
+
+source "$(cd "$(dirname "${BASH_SOURCE[0]}")"/../../../../ && pwd)"/tools/devshell/lib/vars.sh
+fx-config-read
+
+fx-command-run serve-updates &
+# To ensure any children of the above get the signal, we kill our whole process
+# group.
+trap "trap - EXIT; kill 0" EXIT
+sleep 3
+
+export FUCHSIA_IPV4_ADDR=$(fx-command-run shell ifconfig eth | perl -n -e 'print $1 if /inet addr:(\S*)/')
+
+if [[ -z "$FUCHSIA_IPV4_ADDR" ]]; then
+  fx-error "Can not determine IPV4 address of Fuchsia target"
+  exit 1
+fi
+
+export FUCHSIA_SSH_KEY="${FUCHSIA_DIR}/.ssh/pkey"
+
+export FUCHSIA_TEST_OUTDIR="${FUCHSIA_OUT_DIR}/test_out/$(date +'%F-%H:%M:%S')"
+mkdir -p "${FUCHSIA_TEST_OUTDIR}"
+
+"${FUCHSIA_BUILD_DIR}/host_x64/ermine_session_shell_test" "$@"