Adding CFM sanity test and doing some refactoring.

Add CFM sanity test utilizing hangout JS commands to test basic hangouts
functionality like starting a hangout, muting/unmuting mic, exiting
hangout, detecting peripherals and running hotrod diagnostics.

BUG=chromium:597138
TEST=Tested on a local setup with test_that

Change-Id: Ic315f38f1d0f6ecca55a30973cfa9665bbc96bb2
Reviewed-on: https://chromium-review.googlesource.com/334259
Commit-Ready: harpreet Grewal <harpreet@chromium.org>
Tested-by: harpreet Grewal <harpreet@chromium.org>
Reviewed-by: harpreet Grewal <harpreet@chromium.org>
Reviewed-by: Kalin Stoyanov <kalin@chromium.org>
diff --git a/client/common_lib/cros/cfm_util.py b/client/common_lib/cros/cfm_util.py
new file mode 100644
index 0000000..dbf7b1c
--- /dev/null
+++ b/client/common_lib/cros/cfm_util.py
@@ -0,0 +1,290 @@
+# Copyright 2016 The Chromium OS 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 logging
+
+from autotest_lib.client.bin import utils
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib.cros import chrome
+
+DEFAULT_TIMEOUT = 30
+DIAGNOSTIC_RUN_TIMEOUT = 180
+
+def get_cfm_webview_context(browser, ext_id):
+    """Get context for CFM webview.
+
+    @param broswer: Telemetry broswer object.
+    @param ext_id: Extension id of the hangouts app.
+    @return webview context.
+    """
+    ext_contexts = wait_for_hangouts_ext(browser, ext_id)
+
+    for context in ext_contexts:
+        context.WaitForDocumentReadyStateToBeInteractiveOrBetter()
+        tagName = context.EvaluateJavaScript(
+            "document.querySelector('webview') ? 'WEBVIEW' : 'NOWEBVIEW'")
+
+        if tagName == "WEBVIEW":
+            def webview_context():
+                try:
+                    wb_contexts = context.GetWebviewContexts()
+                    if len(wb_contexts) == 1:
+                        return wb_contexts[0]
+                except (KeyError, chrome.Error):
+                    pass
+                return None
+            return utils.poll_for_condition(
+                    webview_context,
+                    exception=error.TestFail('Hangouts webview not available.'),
+                    timeout=DEFAULT_TIMEOUT,
+                    sleep_interval=1)
+
+
+def wait_for_hangouts_ext(browser, ext_id):
+    """Wait for hangouts extension launch.
+
+    @param browser: Telemetry browser object.
+    @param ext_id: Extension id of the hangouts app.
+    @return extension contexts.
+    """
+    def hangout_ext_contexts():
+        try:
+            ext_contexts = browser.extensions.GetByExtensionId(ext_id)
+            if len(ext_contexts) > 1:
+                return ext_contexts
+        except (KeyError, chrome.Error):
+            pass
+        return []
+    return utils.poll_for_condition(
+            hangout_ext_contexts,
+            exception=error.TestFail('Hangouts app failed to launch'),
+            timeout=DEFAULT_TIMEOUT,
+            sleep_interval=1)
+
+
+def wait_for_telemetry_commands(webview_context):
+    """Wait for hotrod app to load and telemetry commands to be available.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    webview_context.WaitForJavaScriptExpression(
+            "typeof window.hrOobIsStartPageForTest == 'function'",
+            DEFAULT_TIMEOUT)
+    logging.info('Hotrod telemetry commands available for testing.')
+
+
+def wait_for_oobe_start_page(webview_context):
+    """Wait for oobe start screen to launch.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    webview_context.WaitForJavaScriptExpression(
+            "window.hrOobIsStartPageForTest() === true;", DEFAULT_TIMEOUT)
+    logging.info('Reached oobe start page')
+
+
+def skip_oobe_screen(webview_context):
+    """Skip Chromebox for Meetings oobe screen.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    webview_context.ExecuteJavaScript("window.hrOobSkipForTest()")
+    utils.poll_for_condition(lambda: not webview_context.EvaluateJavaScript(
+            "window.hrOobIsStartPageForTest()"),
+            exception=error.TestFail('Not able to skip oobe screen.'),
+            timeout=DEFAULT_TIMEOUT,
+            sleep_interval=1)
+    logging.info('Skipped oobe screen.')
+
+
+def start_new_hangout_session(webview_context, hangout_name):
+    """Start a new hangout session.
+
+    @param webview_context: Context for hangouts webview.
+    @param hangout_name: Name of the hangout session.
+    """
+    if not is_ready_to_start_hangout_session(webview_context):
+        if is_in_hangout_session(webview_context):
+            end_hangout_session(webview_context)
+
+    webview_context.ExecuteJavaScript("window.hrStartCallForTest('" +
+                                  hangout_name + "')")
+    utils.poll_for_condition(lambda: webview_context.EvaluateJavaScript(
+            "window.hrIsInHangoutForTest()"),
+            exception=error.TestFail('Not able to start session.'),
+            timeout=DEFAULT_TIMEOUT,
+            sleep_interval=1)
+    logging.info('Started hangout session: %s', hangout_name)
+
+
+def end_hangout_session(webview_context):
+    """End current hangout session.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    webview_context.ExecuteJavaScript("window.hrHangupCallForTest()")
+    utils.poll_for_condition(lambda: not webview_context.EvaluateJavaScript(
+            "window.hrIsInHangoutForTest()"),
+            exception=error.TestFail('Not able to end session.'),
+            timeout=DEFAULT_TIMEOUT,
+            sleep_interval=1)
+
+    logging.info('Ended hangout session.')
+
+
+def mute_audio(webview_context):
+    """Mute mic audio.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    webview_context.ExecuteJavaScript("window.hrMuteAudioForTest()")
+    logging.info('Mute audio.')
+
+
+def unmute_audio(webview_context):
+    """Unmute mic audio.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    webview_context.ExecuteJavaScript("window.hrUnmuteAudioForTest()")
+    logging.info('Unmute audio.')
+
+
+def is_oobe_start_page(webview_context):
+    """Check if device is on CFM oobe start screen.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    if webview_context.EvaluateJavaScript("window.hrOobIsStartPageForTest()"):
+        logging.info('Is on oobe start page.')
+        return True
+    logging.info('Is not on oobe start page.')
+    return False
+
+
+def is_in_hangout_session(webview_context):
+    """Check if device is in hangout session.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    if webview_context.EvaluateJavaScript("window.hrIsInHangoutForTest()"):
+        logging.info('Is in hangout session.')
+        return True
+    logging.info('Is not in hangout session.')
+    return False
+
+
+def is_ready_to_start_hangout_session(webview_context):
+    """Check if device is ready to start a new hangout session.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    if (webview_context.EvaluateJavaScript(
+            "window.hrIsReadyToStartHangoutForTest()")):
+        logging.info('Is ready to start hangout session.')
+        return True
+    logging.info('Is not ready to start hangout session.')
+    return False
+
+
+def is_diagnostic_run_in_progress(webview_context):
+    """Check if hotrod diagnostics is running.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    if (webview_context.EvaluateJavaScript(
+            "window.hrIsDiagnosticRunInProgressForTest()")):
+        logging.info('Diagnostic run is in progress.')
+        return True
+    logging.info('Diagnostic run is not in progress.')
+    return False
+
+
+def wait_for_diagnostic_run_to_complete(webview_context):
+    """Wait for hotrod diagnostics to complete.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    utils.poll_for_condition(lambda: not webview_context.EvaluateJavaScript(
+            "window.hrIsDiagnosticRunInProgressForTest()"),
+            exception=error.TestError('Diagnostic run still in progress after '
+                                      '3 minutes.'),
+            timeout=DIAGNOSTIC_RUN_TIMEOUT,
+            sleep_interval=1)
+
+
+def run_diagnostics(webview_context):
+    """Run hotrod diagnostics.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    if is_diagnostic_run_in_progress(webview_context):
+        wait_for_diagnostic_run_to_complete(webview_context)
+    webview_context.ExecuteJavaScript("window.hrRunDiagnosticsForTest()")
+    logging.info('Started diagnostics run.')
+
+
+def get_last_diagnostics_results(webview_context):
+    """Get latest hotrod diagnostics results.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    if is_diagnostic_run_in_progress(webview_context):
+        wait_for_diagnostic_run_to_complete(webview_context)
+    return webview_context.EvaluateJavaScript(
+            "window.hrGetLastDiagnosticsResultForTest()")
+
+
+def get_mic_devices(webview_context):
+    """Get all mic devices detected by hotrod.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    return webview_context.EvaluateJavaScript(
+            "window.hrGetAudioInDevicesForTest()")
+
+
+def get_speaker_devices(webview_context):
+    """Get all speaker devices detected by hotrod.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    return webview_context.EvaluateJavaScript(
+            "window.hrGetAudioOutDevicesForTest()")
+
+
+def get_camera_devices(webview_context):
+    """Get all camera devices detected by hotrod.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    return webview_context.EvaluateJavaScript(
+            "window.hrGetVideoCaptureDevicesForTest()")
+
+
+def get_preferred_mic(webview_context):
+    """Get mic preferred for hotrod.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    return webview_context.EvaluateJavaScript(
+            "window.hrGetAudioInPrefForTest()")
+
+
+def get_preferred_speaker(webview_context):
+    """Get speaker preferred for hotrod.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    return webview_context.EvaluateJavaScript(
+            "window.hrGetAudioOutPrefForTest()")
+
+
+def get_preferred_camera(webview_context):
+    """Get camera preferred for hotrod.
+
+    @param webview_context: Context for hangouts webview.
+    """
+    return webview_context.EvaluateJavaScript(
+            "window.hrGetVideoCapturePrefForTest()")
diff --git a/client/site_tests/enterprise_CFM_Sanity/control b/client/site_tests/enterprise_CFM_Sanity/control
new file mode 100644
index 0000000..cf4dc3a
--- /dev/null
+++ b/client/site_tests/enterprise_CFM_Sanity/control
@@ -0,0 +1,17 @@
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+AUTHOR = "harpreet@chromium.org"
+NAME = "enterprise_CFM_Sanity"
+TIME = "SHORT"
+TEST_CATEGORY = "Enterprise"
+TEST_CLASS = "enterprise"
+TEST_TYPE = "client"
+
+DOC = """
+This test runs a series of test actions and performs verifications to make sure
+CFM enrolled devices behave as expected.
+"""
+
+job.run_test('enterprise_CFM_Sanity')
diff --git a/client/site_tests/enterprise_CFM_Sanity/enterprise_CFM_Sanity.py b/client/site_tests/enterprise_CFM_Sanity/enterprise_CFM_Sanity.py
new file mode 100644
index 0000000..263dedd
--- /dev/null
+++ b/client/site_tests/enterprise_CFM_Sanity/enterprise_CFM_Sanity.py
@@ -0,0 +1,148 @@
+# Copyright 2016 The Chromium OS 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 datetime, logging, time
+
+from autotest_lib.client.bin import test
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib.cros import chrome, cfm_util
+
+LONG_TIMEOUT = 10
+SHORT_TIMEOUT = 2
+EXT_ID = 'ikfcpmgefdpheiiomgmhlmmkihchmdlj'
+FAILED_TEST_LIST = list()
+
+
+class enterprise_CFM_Sanity(test.test):
+    """Tests the following fuctionality works on CFM enrolled devices:
+           1. Is able to reach the oobe screen
+           2. Is able to start a hangout session
+           3. Should not be able to start a hangout session if already in a
+              session.
+           4. Exits hangout session successfully.
+           5. Should be able to start a hangout session if currently not in
+              a session.
+           6. Is able to detect attached peripherals: mic, speaker, camera.
+           7. Is able to run hotrod diagnostics.
+    """
+    version = 1
+
+
+    def _hangouts_sanity_test(self, webview_context):
+        """Execute a series of test actions and perform verifications.
+
+        @param webview_context: Context for hangouts webview.
+        @raises error.TestFail if any of the checks fail.
+        """
+        current_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+        hangout_name = 'auto-hangout-' + current_time
+
+        cfm_util.wait_for_telemetry_commands(webview_context)
+        cfm_util.wait_for_oobe_start_page(webview_context)
+
+        if not cfm_util.is_oobe_start_page(webview_context):
+            raise error.TestFail('CFM did not reach oobe screen.')
+
+        cfm_util.skip_oobe_screen(webview_context)
+
+        if cfm_util.is_ready_to_start_hangout_session(webview_context):
+            cfm_util.start_new_hangout_session(webview_context, hangout_name)
+
+        if not cfm_util.is_in_hangout_session(webview_context):
+            raise error.TestFail('CFM was not able to start hangout session.')
+
+        time.sleep(LONG_TIMEOUT)
+        cfm_util.unmute_audio(webview_context)
+
+        if cfm_util.is_ready_to_start_hangout_session(webview_context):
+            raise error.TestFail('Is already in hangout session and should not '
+                                 'be able to start another session.')
+
+        if cfm_util.is_oobe_start_page(webview_context):
+            raise error.TestFail('CFM should be in hangout session and not on '
+                                 'oobe screen.')
+
+        time.sleep(SHORT_TIMEOUT)
+        cfm_util.mute_audio(webview_context)
+        time.sleep(SHORT_TIMEOUT)
+        cfm_util.end_hangout_session(webview_context)
+
+        if cfm_util.is_in_hangout_session(webview_context):
+            raise error.TestFail('CFM should not be in hangout session.')
+
+        if cfm_util.is_oobe_start_page(webview_context):
+            raise error.TestFail('CFM should not be on oobe screen.')
+
+        if not cfm_util.is_ready_to_start_hangout_session(webview_context):
+            raise error.TestFail('CFM should be in read state to start hangout '
+                           'session.')
+
+
+    def _peripherals_sanity_test(self, webview_context):
+        """Checks for connected peripherals.
+
+        @param webview_context: Context for hangouts webview.
+        """
+        cfm_util.wait_for_telemetry_commands(webview_context)
+
+        time.sleep(SHORT_TIMEOUT)
+
+        if not cfm_util.get_mic_devices(webview_context):
+            FAILED_TEST_LIST.append('No mic detected')
+
+        if not cfm_util.get_speaker_devices(webview_context):
+            FAILED_TEST_LIST.append('No speaker detected')
+
+        if not cfm_util.get_camera_devices(webview_context):
+            FAILED_TEST_LIST.append('No camera detected')
+
+        if not cfm_util.get_preferred_mic(webview_context):
+            FAILED_TEST_LIST.append('No preferred mic')
+
+        if not cfm_util.get_preferred_speaker(webview_context):
+            FAILED_TEST_LIST.append('No preferred speaker')
+
+        if not cfm_util.get_preferred_camera(webview_context):
+            FAILED_TEST_LIST.append('No preferred camera')
+
+
+    def _diagnostics_sanity_test(self, webview_context):
+        """Runs hotrod diagnostics and checks status.
+
+        @param webview_context: Context for hangouts webview.
+        @raise error.TestFail if diagnostic checks fail.
+        """
+        cfm_util.wait_for_telemetry_commands(webview_context)
+
+        if cfm_util.is_diagnostic_run_in_progress(webview_context):
+            raise error.TestFail('Diagnostics should not be running.')
+
+        cfm_util.run_diagnostics(webview_context)
+
+        if not cfm_util.is_diagnostic_run_in_progress(webview_context):
+            raise error.TestFail('Diagnostics should be running.')
+
+        diag_results = cfm_util.get_last_diagnostics_results(webview_context)
+
+        if diag_results['status'] not in 'success':
+            logging.debug(diag_results['childrens'])
+            FAILED_TEST_LIST.append('Diagnostics failed')
+
+
+    def run_once(self):
+        """Runs the test."""
+        with chrome.Chrome(clear_enterprise_policy=False,
+                           dont_override_profile=True,
+                           disable_gaia_services=False,
+                           disable_default_apps=False,
+                           auto_login=False) as cr:
+            cfm_webview_context = cfm_util.get_cfm_webview_context(
+                    cr.browser, EXT_ID)
+            self._hangouts_sanity_test(cfm_webview_context)
+            self._peripherals_sanity_test(cfm_webview_context)
+            self._diagnostics_sanity_test(cfm_webview_context)
+
+        if FAILED_TEST_LIST:
+            raise error.TestFail('Test failed because of following reasons: %s'
+                                 % ', '.join(map(str, FAILED_TEST_LIST)))
diff --git a/client/site_tests/enterprise_RemoraRequisition/credentials.txt b/client/site_tests/enterprise_RemoraRequisition/credentials.txt
index f6157e1..a8e8614 100644
--- a/client/site_tests/enterprise_RemoraRequisition/credentials.txt
+++ b/client/site_tests/enterprise_RemoraRequisition/credentials.txt
@@ -1 +1 @@
-hotrodmtv@remora-test.mygbiz.com:test0000
+cfmtest@croste.tv:test0000
diff --git a/client/site_tests/enterprise_RemoraRequisition/enterprise_RemoraRequisition.py b/client/site_tests/enterprise_RemoraRequisition/enterprise_RemoraRequisition.py
index ee4ee2e..1999c3f 100644
--- a/client/site_tests/enterprise_RemoraRequisition/enterprise_RemoraRequisition.py
+++ b/client/site_tests/enterprise_RemoraRequisition/enterprise_RemoraRequisition.py
@@ -6,7 +6,7 @@
 
 from autotest_lib.client.bin import test, utils
 from autotest_lib.client.common_lib import error
-from autotest_lib.client.common_lib.cros import chrome, enrollment
+from autotest_lib.client.common_lib.cros import chrome, enrollment, cfm_util
 
 TIMEOUT = 20
 
@@ -14,26 +14,11 @@
     """Enroll as a Remora device."""
     version = 1
 
-    _HANGOUTS_EXT_ID = 'acdafoiapclbpdkhnighhilgampkglpc'
-
-    def _WaitForHangouts(self, browser):
-        def _HangoutExtContexts():
-            try:
-                ext_contexts = browser.extensions.GetByExtensionId(
-                        self._HANGOUTS_EXT_ID)
-                if len(ext_contexts) > 1:
-                    return ext_contexts
-            except (KeyError, chrome.Error):
-                pass
-            return []
-        return utils.poll_for_condition(
-                _HangoutExtContexts,
-                exception=error.TestFail('Hangouts app failed to launch'),
-                timeout=30,
-                sleep_interval=1)
+    _HANGOUTS_EXT_ID = 'ikfcpmgefdpheiiomgmhlmmkihchmdlj'
 
     def _CheckHangoutsExtensionContexts(self, browser):
-        ext_contexts = self._WaitForHangouts(browser)
+        ext_contexts = cfm_util.wait_for_hangouts_ext(
+                browser, self._HANGOUTS_EXT_ID)
         ext_urls = set([context.EvaluateJavaScript('location.href;')
                        for context in ext_contexts])
         expected_urls = set(
diff --git a/server/site_tests/enterprise_CFM_SanityServer/control b/server/site_tests/enterprise_CFM_SanityServer/control
new file mode 100644
index 0000000..522f72a
--- /dev/null
+++ b/server/site_tests/enterprise_CFM_SanityServer/control
@@ -0,0 +1,25 @@
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+AUTHOR = "harpreet@chromium.org"
+NAME = "enterprise_CFM_SanityServer"
+TIME = "SHORT"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "enterprise"
+TEST_TYPE = "server"
+ATTRIBUTES = "suite:hotrod"
+
+DOC = """
+This test clears the TPM if necessary, kicks off a client side test that enrolls
+a device as a remora device and runs a client sanity test followed by clearing
+the TPM again. Every time the TPM is cleared, the system is rebooted.
+"""
+
+
+def run_test(machine):
+    host = hosts.create_host(machine)
+    job.run_test('enterprise_CFM_SanityServer', host=host)
+
+
+parallel_simple(run_test, machines)
diff --git a/server/site_tests/enterprise_CFM_SanityServer/enterprise_CFM_SanityServer.py b/server/site_tests/enterprise_CFM_SanityServer/enterprise_CFM_SanityServer.py
new file mode 100644
index 0000000..3a3802c
--- /dev/null
+++ b/server/site_tests/enterprise_CFM_SanityServer/enterprise_CFM_SanityServer.py
@@ -0,0 +1,21 @@
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from autotest_lib.client.common_lib.cros import tpm_utils
+from autotest_lib.server import test, autotest
+
+
+class enterprise_CFM_SanityServer(test.test):
+    """A test that clears the TPM and runs enterprise_RemoraRequisition to
+    enroll the device into Chromebox for Meetings. After successful enrollment,
+    it runs the CFM sanity test."""
+    version = 1
+
+    def run_once(self, host=None):
+        self.client = host
+
+        tpm_utils.ClearTPMOwnerRequest(self.client)
+        autotest.Autotest(self.client).run_test('enterprise_RemoraRequisition')
+        autotest.Autotest(self.client).run_test('enterprise_CFM_Sanity')
+        tpm_utils.ClearTPMOwnerRequest(self.client)