cros ca: Enable Google Meet for CrOS

- Modified the autologin script to allow more options to be passed
  to CrOS Chrome during auto login.
  - Scripts are borrowed from autotest autologin.py.
- Add related Chrome options in Google Meet test.
  - This is to bypass the camera/microphone permission granting for
    Google Meet.
- Support --verbose logging level config
  - Default logging level is "INFO", with verbose mode, the logging
    level is "DEBUG"

BUG=b:336637815
TEST=python bin/test_cros_remote.py --var=duration=3 --var=typing_delay=0.01 --var=meet_ttl=5 <DUT> cuj.GoogleMeet

Change-Id: Idc8b6b1319ad1384af0226ea88781e7875ca05e0
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/5482558
Reviewed-by: Willis Kung <williskung@google.com>
Tested-by: Xibin Liu <xliu@cienet.com>
Commit-Queue: Xibin Liu <xliu@cienet.com>
diff --git a/contrib/cros_ca_linux/bin/test_cros_local.py b/contrib/cros_ca_linux/bin/test_cros_local.py
index ad68ded..e535db9 100644
--- a/contrib/cros_ca_linux/bin/test_cros_local.py
+++ b/contrib/cros_ca_linux/bin/test_cros_local.py
@@ -26,6 +26,7 @@
 sys.path.insert(0, venv_package)
 
 from bin import utils
+import lib.config as cf
 from lib.host import cros_local_host
 from lib.runner import local_runner
 import lib.utils.logging as lg
@@ -34,6 +35,9 @@
 # pylint: enable=wrong-import-position
 
 logger = lg.logger
+# Remove the standard output from the local runner to avoid the logs
+# flushing into SSH pipe. Logs will be written to files.
+logger.removeHandler(lg.stream_handler)
 
 
 def parse_arguments(argv):
@@ -50,6 +54,7 @@
             are not present.
     """
     parser = argparse.ArgumentParser(description="Run tests on CrOS locally.")
+    utils.add_config(parser)
     utils.add_vars(parser)
     utils.add_tests(parser)
     utils.add_run_id(parser)
@@ -65,6 +70,7 @@
     """
     arguments = parse_arguments(argv)
     variables = utils.parse_vars(arguments.variables)
+    cf.args_to_config(arguments)
 
     start_file = f"{df.PROJECT_ROOT_DIR}/{df.TEST_START_FILE}"
     end_file = f"{df.PROJECT_ROOT_DIR}/{df.TEST_END_FILE}"
diff --git a/contrib/cros_ca_linux/bin/test_cros_remote.py b/contrib/cros_ca_linux/bin/test_cros_remote.py
index 824f16f..cdd77f8 100644
--- a/contrib/cros_ca_linux/bin/test_cros_remote.py
+++ b/contrib/cros_ca_linux/bin/test_cros_remote.py
@@ -17,6 +17,7 @@
 
 from bin import utils
 from lib import definition as df
+import lib.config as cf
 from lib.host import cros_paramiko_host
 from lib.host import cros_ssh_host
 from lib.runner import remote_runner_direct
@@ -44,6 +45,7 @@
     parser = argparse.ArgumentParser(description="Run tests on CrOS.")
     utils.add_vars(parser)
     utils.add_target(parser)
+    utils.add_config(parser)
     utils.add_transport(parser)
     utils.add_tests(parser)
     # With the above config, the program accept these optional arguments:
@@ -73,6 +75,7 @@
     port = ssh_specs["port"]
     username = ssh_specs["user"]
     variables = utils.parse_vars(arguments.variables)
+    cf.args_to_config(arguments)
 
     if arguments.transport == "ssh":
         # When --transport=ssh argument is given.
diff --git a/contrib/cros_ca_linux/bin/test_win_local.py b/contrib/cros_ca_linux/bin/test_win_local.py
index 22962c4..41fd197 100644
--- a/contrib/cros_ca_linux/bin/test_win_local.py
+++ b/contrib/cros_ca_linux/bin/test_win_local.py
@@ -17,16 +17,20 @@
     sys.path.append(str(Path(__file__).resolve().parents[1]))
 
 from bin import utils
+import lib.config as cf
 import lib.definition as df
 from lib.host import win_local_host
 from lib.runner import local_runner
 from lib.utils import logging as lg
 
 
-logger = lg.logger
-
 # pylint: enable=wrong-import-position
 
+logger = lg.logger
+# Remove the standard output from the local runner to avoid the logs
+# flushing into SSH pipe. Logs will be written to files.
+logger.removeHandler(lg.stream_handler)
+
 
 def parse_arguments(argv):
     """Parse command line arguments
@@ -45,6 +49,7 @@
         description="Run tests on Windows locally."
     )
     utils.add_vars(parser)
+    utils.add_config(parser)
     utils.add_tests(parser)
     utils.add_run_id(parser)
 
@@ -59,6 +64,7 @@
     """
     arguments = parse_arguments(argv)
     variables = utils.parse_vars(arguments.variables)
+    cf.args_to_config(arguments)
 
     start_file = f"{df.PROJECT_ROOT_DIR}/{df.TEST_START_FILE}"
     end_file = f"{df.PROJECT_ROOT_DIR}/{df.TEST_END_FILE}"
diff --git a/contrib/cros_ca_linux/bin/test_win_remote.py b/contrib/cros_ca_linux/bin/test_win_remote.py
index aca60a2..a5a597e 100644
--- a/contrib/cros_ca_linux/bin/test_win_remote.py
+++ b/contrib/cros_ca_linux/bin/test_win_remote.py
@@ -16,6 +16,7 @@
     sys.path.append(str(Path(__file__).resolve().parents[1]))
 
 from bin import utils
+import lib.config as cf
 import lib.definition as df
 from lib.host import win_paramiko_host
 from lib.host import win_ssh_host
@@ -44,6 +45,7 @@
     parser = argparse.ArgumentParser(description="Run tests on Windows.")
     utils.add_vars(parser)
     utils.add_transport(parser)
+    utils.add_config(parser)
     utils.add_target(parser)
     utils.add_tests(parser)
     # With the above config, the program accept these optional arguments:
@@ -73,6 +75,7 @@
     port = ssh_specs["port"]
     username = ssh_specs["user"]
     variables = utils.parse_vars(arguments.variables)
+    cf.args_to_config(arguments)
 
     if arguments.transport == "ssh":
         # When --transport=ssh argument is given.
diff --git a/contrib/cros_ca_linux/bin/utils.py b/contrib/cros_ca_linux/bin/utils.py
index 0d70088..feb3aba 100644
--- a/contrib/cros_ca_linux/bin/utils.py
+++ b/contrib/cros_ca_linux/bin/utils.py
@@ -8,6 +8,11 @@
 import re
 from typing import List
 
+import lib.config as cf
+
+
+config = cf.config
+
 
 def add_vars(parser: argparse.ArgumentParser):
     """Add var optionatl argument to parser"""
@@ -22,6 +27,16 @@
     )
 
 
+def parse_vars(arg_vars: List[str]) -> dict:
+    """Parse the variables list into key / value dictionary"""
+    variables = {}
+    if arg_vars:
+        for var in arg_vars:
+            key, value = var.split("=")
+            variables[key] = value
+    return variables
+
+
 def add_run_id(parser: argparse.ArgumentParser):
     """Add run_id optionatl argument to parser"""
     parser.add_argument(
@@ -42,6 +57,17 @@
     )
 
 
+def add_config(parser: argparse.ArgumentParser):
+    """Add config optionatl argument to parser"""
+    parser.add_argument(
+        "--verbose",
+        dest="verbose",
+        action="store_true",
+        help="Verbose / debug logging mode.",
+    )
+    # Add other confiugrations after this.
+
+
 def add_target(parser: argparse.ArgumentParser):
     """Add target argument to parser"""
     parser.add_argument(
@@ -59,16 +85,6 @@
     )
 
 
-def parse_vars(arg_vars: List[str]) -> dict:
-    """Parse the variables list into key / value dictionary"""
-    variables = {}
-    if arg_vars:
-        for var in arg_vars:
-            key, value = var.split("=")
-            variables[key] = value
-    return variables
-
-
 def parse_ssh_host_spec(host_spec, platform="ChromeOS"):
     """Parses an SSH host spec string into a dictionary.
 
diff --git a/contrib/cros_ca_linux/lib/config.py b/contrib/cros_ca_linux/lib/config.py
new file mode 100644
index 0000000..c7cd8b7
--- /dev/null
+++ b/contrib/cros_ca_linux/lib/config.py
@@ -0,0 +1,33 @@
+# Copyright 2024 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Program global configuration."""
+
+from typing import NamedTuple
+
+import lib.utils.logging as lg
+
+
+class Config:
+    """Global configuration class."""
+
+    verbose: bool = False
+
+
+config = Config()
+
+
+def args_to_config(arguments: NamedTuple):
+    """Parse arguments to config"""
+    config.verbose = arguments.verbose
+    if config.verbose:
+        lg.logger.setLevel(lg.logging.DEBUG)
+        lg.logger.debug("Running with verbose mode.")
+
+
+def config_to_args():
+    args = ""
+    if config.verbose:
+        args += "--verbose"
+    return args
diff --git a/contrib/cros_ca_linux/lib/definition.py b/contrib/cros_ca_linux/lib/definition.py
index 796ad00..c70c1ed 100644
--- a/contrib/cros_ca_linux/lib/definition.py
+++ b/contrib/cros_ca_linux/lib/definition.py
@@ -7,7 +7,6 @@
 import pathlib
 
 
-
 # ==== Path for project folders and files
 PROJECT_LIB_DIR = pathlib.Path(__file__).parent.resolve()
 PROJECT_ROOT_DIR = PROJECT_LIB_DIR.parent
diff --git a/contrib/cros_ca_linux/lib/host/base_host.py b/contrib/cros_ca_linux/lib/host/base_host.py
index bf6851d..9e7dd5a 100644
--- a/contrib/cros_ca_linux/lib/host/base_host.py
+++ b/contrib/cros_ca_linux/lib/host/base_host.py
@@ -44,6 +44,7 @@
     username: str = None
     password: str = None
     arc: bool = False  # For CrOS to bring up ARC.
+    chrome_args: List[str] = []
 
 
 class BaseHost:
@@ -98,7 +99,7 @@
             if self.hostname:
                 logger.info("Connected to host: %s", self.hostname)
             self._connected = True
-        except Exception as e:
+        except Exception:
             self._connected = False
 
             raise  # Re-raise the exception for caller handling
@@ -378,7 +379,7 @@
                 location = winreg.QueryValueEx(key, downloads_guid)[0]
             return location
         else:
-            return os.path.join(os.path.expanduser("~"), "downloads")
+            return "/home/chronos/user/Downloads"
 
     def deploy(self) -> None:
         """Deploy the environment for testing"""
diff --git a/contrib/cros_ca_linux/lib/host/cros_local_host.py b/contrib/cros_ca_linux/lib/host/cros_local_host.py
index 73cba30..9436a77 100644
--- a/contrib/cros_ca_linux/lib/host/cros_local_host.py
+++ b/contrib/cros_ca_linux/lib/host/cros_local_host.py
@@ -15,6 +15,7 @@
 from selenium.webdriver.chrome.service import Service
 
 from lib.host import base_host
+from lib.utils.autotest import autologin
 
 
 class CrosLocalHost(base_host.BaseHost):
@@ -39,27 +40,17 @@
             RuntimeError: if autologin fails.
         """
 
-        command = "/usr/local/autotest/bin/autologin.py "
-        # Don't keep state. Delete existing profile.
-        # This is the only way it can work together with gaia login. Without
-        # this option, if another account has already exists on the DUT,
-        # the GAIA login would fail on the screen of "Choose your setup"
-        # for personal use, for a child or for work.
-        command += "-d "
-        if option.arc:
-            command += "--arc --no-arc-syncs "
-        if option.username and option.password:
-            command += f"-u {option.username} -p {option.password} "
-        # TODO: Other Chrome options can be added here. These options
-        #       can be set in the "option" class and applied to the
-        #       autologin.py argument here.
-
-        subprocess.run(
-            command,
-            shell=True,
-            stdout=subprocess.DEVNULL,  # Supress the stdout and stderr
-            stderr=subprocess.DEVNULL,
-            check=True,
+        autologin.autologin(
+            # This is the only way it can work together with gaia login. Without
+            # this option, if another account has already exists on the DUT,
+            # the GAIA login would fail on the screen of "Choose your setup"
+            # for personal use, for a child or for work.
+            dont_override_profile=True,
+            arc=option.arc,
+            no_arc_syncs=option.arc,
+            username=option.username,
+            password=option.password,
+            extra_args=option.chrome_args,
         )
         time.sleep(2)
 
diff --git a/contrib/cros_ca_linux/lib/host/ssh/port_forwarding.py b/contrib/cros_ca_linux/lib/host/ssh/port_forwarding.py
index e2a4964..2c0a02d 100644
--- a/contrib/cros_ca_linux/lib/host/ssh/port_forwarding.py
+++ b/contrib/cros_ca_linux/lib/host/ssh/port_forwarding.py
@@ -4,8 +4,6 @@
 
 """Implement a call that can do the SSH port forwarding."""
 
-import os
-import signal
 import subprocess
 import time
 
@@ -98,7 +96,7 @@
         Terminates the SSH process established for the port forwarding tunnel.
         """
         if self.ssh_process:
-            os.killpg(os.getpgid(self.ssh_process.pid), signal.SIGTERM)
+            self.ssh_process.kill()
             self.ssh_process = None
             logger.info(
                 "Port forwarding for %s has been terminated.", self.local_port
diff --git a/contrib/cros_ca_linux/lib/runner/remote_runner_direct.py b/contrib/cros_ca_linux/lib/runner/remote_runner_direct.py
index 492caaf..d3e73e5 100644
--- a/contrib/cros_ca_linux/lib/runner/remote_runner_direct.py
+++ b/contrib/cros_ca_linux/lib/runner/remote_runner_direct.py
@@ -7,6 +7,7 @@
 import time
 from typing import List
 
+import lib.config as cf
 import lib.definition as df
 from lib.host import base_host
 from lib.runner import base_runner as br
@@ -28,6 +29,7 @@
         script += (
             f" --run_id={run_id}"  # Provide run_id so test_end can be tracked.
         )
+        script += f" {cf.config_to_args()}"
         for k in variables:
             script += f" --var={k}={variables[k]}"
         script += f" {parsed_test.test_name}"
@@ -40,7 +42,7 @@
         # Create a temporary file on the host to get the DUT file time.
         tmp_name = ".file_no_use"
         tmp_file = f"{self.dut.test_dir}/{tmp_name}"
-        self.dut.write_file(tmp_file, f"this file can be removed")
+        self.dut.write_file(tmp_file, "this file can be removed")
         entries = self.dut.dir_entries(f"{self.dut.test_dir}")
         mtime = 0
         for e in entries:
@@ -49,6 +51,7 @@
                 break
         self.dut.remove_path(tmp_file)
 
+        self.logger.debug("Run the script locally on the DUT: %s", script)
         # Run the command in asynchronous mode.
         _, _, is_done = self.dut.executing_command(script, timeout=10)
         start_time = time.time()  # Record the start time
diff --git a/contrib/cros_ca_linux/lib/runner/remote_runner_script.py b/contrib/cros_ca_linux/lib/runner/remote_runner_script.py
index ada299e..80057ef 100644
--- a/contrib/cros_ca_linux/lib/runner/remote_runner_script.py
+++ b/contrib/cros_ca_linux/lib/runner/remote_runner_script.py
@@ -7,6 +7,7 @@
 import time
 from typing import List
 
+import lib.config as cf
 import lib.definition as df
 from lib.host import base_host
 from lib.runner import base_runner as br
@@ -32,12 +33,14 @@
         script += (
             f" --run_id={run_id}"  # Provide run_id so test_end can be tracked.
         )
+        script += f" {cf.config_to_args()}"
         for k in variables:
             script += f" --var={k}={variables[k]}"
         script += f" {parsed_test.test_name}"
 
         self.dut.make_dir(starter_path)
         self.dut.remove_path(f"{starter_path}/test_ended.txt")
+        self.logger.debug("Run the script locally on the DUT: %s", script)
         self.dut.write_file(f"{starter_path}/run.bat", script)
         self.dut.write_file(f"{starter_path}/start_test.txt", "")
 
diff --git a/contrib/cros_ca_linux/lib/testing/lib/chrome_browser_test.py b/contrib/cros_ca_linux/lib/testing/lib/chrome_browser_test.py
index 1eb3b38..88c23b0 100644
--- a/contrib/cros_ca_linux/lib/testing/lib/chrome_browser_test.py
+++ b/contrib/cros_ca_linux/lib/testing/lib/chrome_browser_test.py
@@ -123,7 +123,7 @@
                 driver, metric, False
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot (open - full): %s -- %s",
                 metric,
                 histogramSnapshot,
@@ -141,12 +141,12 @@
             # Switch to monitoring mode
             driver.find_element(By.ID, "enable_monitoring").click()
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(f"Monitoring mode enabled {metric}")
+            self.logger.info("Monitoring mode enabled %s", metric)
             histogramSnapshot = self._get_histogram_snapshot(
                 driver, metric, True
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot (monitor - delta): %s -- %s",
                 metric,
                 histogramSnapshot,
@@ -155,7 +155,7 @@
                 driver, metric, False
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot (monitor - full): %s -- %s",
                 metric,
                 histogramSnapshot,
@@ -168,7 +168,7 @@
                 driver, metric, True
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot (%s - delta): %s -- %s",
                 annotation,
                 metric,
@@ -178,7 +178,7 @@
                 driver, metric, False
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot ({annotation} - full): %s -- %s",
                 metric,
                 histogramSnapshot,
@@ -200,8 +200,9 @@
 
             # capture screenshot before monitoring mode disabled
             if not self.save_screenshot(f"stopping_{metric}"):
-                self.loggger.debug(
-                    f"Failed to save a screenshot on DUT when stopping {metric}"
+                self.logger.info(
+                    "Failed to save a screenshot on DUT when stopping %s",
+                    metric,
                 )
             time.sleep(0.5)
 
@@ -210,7 +211,7 @@
                 driver, metric, True
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot (before monitoring stopped - delta): "
                 + "%s -- %s",
                 metric,
@@ -220,7 +221,7 @@
                 driver, metric, False
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot (before monitoring stopped - full): "
                 + "%s -- %s",
                 metric,
@@ -230,9 +231,9 @@
             # Stop monitoring mode
             driver.find_element(By.ID, "stop").click()
             time.sleep(0.25)
-            self.logger.debug(f"monitoring mode stopped: {metric}")
+            self.logger.info("monitoring mode stopped: %s", metric)
             if not self.save_screenshot(f"stopped_{metric}"):
-                self.loggger.debug(
+                self.logger.info(
                     "Failed to save a screenshot on DUT after stopping %s",
                     metric,
                 )
@@ -243,7 +244,7 @@
                 driver, metric, True
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot (monitoring stopped - delta): %s -- %s",
                 metric,
                 histogramSnapshot,
@@ -252,7 +253,7 @@
                 driver, metric, False
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot (monitoring stopped - full): %s -- %s",
                 metric,
                 histogramSnapshot,
@@ -277,7 +278,7 @@
                     ).text
                     word = header_text.split(" ")
                     samples, mean_value = word[3], word[7]
-                    self.logger.debug(
+                    self.logger.info(
                         "%s mean: %s samples: %s",
                         histogram_name,
                         mean_value,
@@ -289,18 +290,18 @@
                     histogram_body = histograms.find_elements(
                         By.XPATH, "//div[@class='histogram-body']//p"
                     )[i]
-                    self.logger.debug(
-                        f"histogram body: \n{histogram_body.text}"
+                    self.logger.info(
+                        "histogram body: \n%s", histogram_body.text
                     )
             except Exception:
-                self.logger.warning(f"Failed to get data from {metric}")
+                self.logger.warning("Failed to get data from %s", metric)
 
             histogram_dict[metric] = {"sample": samples, "mean": mean_value}
             histogramSnapshot = self._get_histogram_snapshot(
                 driver, metric, False
             )
             time.sleep(self.getHistogram_wait_time)
-            self.logger.debug(
+            self.logger.info(
                 "histogram snapshot (get data - full): %s -- %s",
                 metric,
                 histogramSnapshot,
diff --git a/contrib/cros_ca_linux/lib/testing/local/cuj/GoogleMeet.py b/contrib/cros_ca_linux/lib/testing/local/cuj/GoogleMeet.py
index cde3404..827c934 100644
--- a/contrib/cros_ca_linux/lib/testing/local/cuj/GoogleMeet.py
+++ b/contrib/cros_ca_linux/lib/testing/local/cuj/GoogleMeet.py
@@ -9,6 +9,7 @@
 
 import json
 import os
+import shutil
 import subprocess
 import time
 
@@ -18,6 +19,7 @@
 from selenium.webdriver.support import expected_conditions as EC
 from selenium.webdriver.support.wait import WebDriverWait
 
+from lib.host import base_host
 from lib.testing.lib import chrome_browser_test as cbt
 
 
@@ -73,31 +75,46 @@
         return opt
 
     def run(self):
+        if os.name != "nt":
+            # Only do this for CrOS
+            option = base_host.LoginOption()
+            option.username = self.var("gaia_account")
+            option.password = self.var("gaia_password")
+            option.arc = True
+            option.chrome_args = [
+                "--use-fake-ui-for-media-stream",
+            ]
+            self.dut.auto_login(option)
+        self.dut.mute(True)
+
         # Get the webdriver at the beginning of the test.
         driver = self.get_webdriver()
         wait = WebDriverWait(driver, 15)
 
-        # Sign in with the Google account (index = 0)
-        driver.get("https://accounts.google.com/ServiceLogin")
-        driver.find_element(By.TAG_NAME, "input").send_keys(
-            self.var("gaia_account")
-        )
-        driver.find_element(By.XPATH, '//span[text()="Next"]').find_element(
-            By.XPATH, "./.."
-        ).click()
-        _ = wait.until(
-            EC.presence_of_element_located(
-                (By.XPATH, '//span[text()="Welcome"]')
+        if os.name == "nt":
+            # Only do this for Windows. For CrOS, the DUT has been signed in
+            # with GAIA login.
+            # Sign in with the Google account (index = 0)
+            driver.get("https://accounts.google.com/ServiceLogin")
+            driver.find_element(By.TAG_NAME, "input").send_keys(
+                self.var("gaia_account")
             )
-        )
-        submit = driver.find_element(By.ID, "passwordNext")
-        _ = wait.until(EC.element_to_be_clickable(submit))
-        driver.find_element(By.NAME, "Passwd").send_keys(
-            self.var("gaia_password")
-        )
-        submit.click()
-        # time.sleep(send_keys_wait_time)
-        time.sleep(0.1)
+            driver.find_element(By.XPATH, '//span[text()="Next"]').find_element(
+                By.XPATH, "./.."
+            ).click()
+            _ = wait.until(
+                EC.presence_of_element_located(
+                    (By.XPATH, '//span[text()="Welcome"]')
+                )
+            )
+            submit = driver.find_element(By.ID, "passwordNext")
+            _ = wait.until(EC.element_to_be_clickable(submit))
+            driver.find_element(By.NAME, "Passwd").send_keys(
+                self.var("gaia_password")
+            )
+            submit.click()
+            # time.sleep(send_keys_wait_time)
+            time.sleep(0.1)
 
         # Open WebRTC Internals page (index = 0)
         driver.get("chrome://webrtc-internals")
@@ -157,7 +174,6 @@
             By.CSS_SELECTOR, 'button[aria-label="Turn off camera (ctrl + e)"]'
         )
         camera_button.click()
-        driver.implicitly_wait(2)
 
         chat_button = wait.until(
             EC.element_to_be_clickable(
@@ -168,7 +184,6 @@
             )
         )
         chat_button.click()
-        driver.implicitly_wait(2)
 
         text_area = wait.until(
             EC.element_to_be_clickable(
@@ -180,7 +195,6 @@
             )
         )
         text_area.click()
-        driver.implicitly_wait(2)
 
         # capture metric snapshot after meet call effect is in place and stable
         self.get_histogram_snapshots("meet effect stablized and before typing")
@@ -260,7 +274,7 @@
         self.get_histogram_snapshots("after typing")
 
         self.logger.info(
-            f"Typing stopped. Final counter value after typing: {counter}"
+            "Typing stopped. Final counter value after typing: %s", counter
         )
 
         if not self.save_screenshot("after_typing"):
@@ -280,10 +294,10 @@
                 capture_output=True,
                 check=True,
             )
-            self.logger.info("tasklist output:\n", task_output.stdout)
+            self.logger.info("tasklist output:\n%s", task_output.stdout)
 
             etl_path = self.test_dir / "test.etl"
-            self.logger.info(f"xperf -d {etl_path}")
+            self.logger.info("xperf -d %s", etl_path)
             # subprocess.call(["xperf", "-d", "test.etl"])
             xperf_output = subprocess.run(
                 ["xperf", "-d", etl_path],
@@ -292,7 +306,7 @@
                 capture_output=True,
                 check=True,
             )
-            self.logger.info("xperf utput:\n", xperf_output.stdout)
+            self.logger.info("xperf utput:\n%s", xperf_output.stdout)
 
         # If the dump file exists, remove it.
         file_path = os.path.join(
@@ -314,14 +328,14 @@
         histogram_dict = self.get_historam_data()
 
         driver.quit()
-        self.logger.info(f"Metrics: {histogram_dict}")
+        self.logger.info("Metrics: %s", histogram_dict)
         # Add the data of dump file to result.
         with open(file_path, "r", encoding="utf-8") as f:
             webrtc_dump = json.load(f)
             histogram_dict["webrtc"] = webrtc_dump
 
         dst = os.path.join(self.histogram_dir, "webrtc_internals_dump.txt")
-        os.rename(file_path, dst)
+        shutil.move(file_path, dst)
 
         # Save data to result.json file.
         with open(
diff --git a/contrib/cros_ca_linux/lib/utils/autotest/autologin.py b/contrib/cros_ca_linux/lib/utils/autotest/autologin.py
new file mode 100644
index 0000000..4d5ae71
--- /dev/null
+++ b/contrib/cros_ca_linux/lib/utils/autotest/autologin.py
@@ -0,0 +1,92 @@
+# Copyright 2024 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Does the CrOS auto login. Borrowed from autotest."""
+
+import sys
+from typing import List
+
+
+# pylint: disable=wrong-import-position
+
+# Import autotest client
+client_dir = "/usr/local/autotest/bin"
+sys.path.insert(0, client_dir)
+# "common" does all the autotest lib setup.
+import common  # pylint: disable=unused-import
+
+
+sys.path.pop(0)
+
+from autotest_lib.client.common_lib.cros import chrome
+
+
+# pylint: enable=wrong-import-position
+
+
+def autologin(
+    arc: bool = False,
+    arc_timeout: int = None,
+    no_arc_syncs: bool = False,
+    disable_arc_cpu_restriction: bool = False,
+    username: str = None,
+    password: str = None,
+    enable_default_apps: bool = False,
+    dont_override_profile: bool = False,
+    no_startup_window: bool = False,
+    url: str = "",
+    enable_features: List[str] = None,
+    disable_features: List[str] = None,
+    extra_args: List[str] = None,
+):
+    """ChromeOS auto login.
+
+    Args:
+        arc: Flag to enable ARC.
+        arc_timeout: The timeout value waiting for ARC.
+        no_arc_syncs: Flag to disable ARC syncs.
+        disable_arc_cpu_restriction: Flag to disable ARC CPU restriction.
+        username: User name. Do GAIA login if the user is given.
+        password: Password for the user.
+        enable_default_apps: Flag to enable default apps.
+        dont_override_profile: Flag to disable profile overriding.
+        no_startup_window: Don't open a Chrome window on startup.
+        url: The URL to navigate to when starting up the Chrome.
+        enable_features: Enable the given features.
+        disable_features: Disable the given features.
+        extra_args: Extra chrome args to pass to the Chrome process.
+            For example:
+            - pass in "--use-fake-ui-for-media-stream" to grant media
+              permission for Google Meet.
+            - pass in "--enable-benchmarking" to enable benchmark testing.
+    """
+    browser_args = []
+    if no_startup_window:
+        browser_args.append("--no-startup-window")
+    if enable_features and len(enable_features) > 0:
+        browser_args.append("--enable-features=%s" % ",".join(enable_features))
+    if disable_features and len(disable_features) > 0:
+        browser_args.append(
+            "--disable-features=%s" % ",".join(disable_features)
+        )
+    if extra_args and len(extra_args) > 0:
+        browser_args.extend(extra_args)
+
+    # Avoid calling close() on the Chrome object; this keeps the session active.
+    cr = chrome.Chrome(
+        extra_browser_args=browser_args,
+        arc_mode=("enabled" if arc else None),
+        arc_timeout=arc_timeout,
+        disable_arc_cpu_restriction=disable_arc_cpu_restriction,
+        disable_app_sync=no_arc_syncs,
+        disable_play_auto_install=no_arc_syncs,
+        username=username,
+        password=(password if username else None),
+        gaia_login=(username is not None),
+        disable_default_apps=(not enable_default_apps),
+        dont_override_profile=dont_override_profile,
+    )
+    if url:
+        tab = cr.browser.tabs[0]
+        tab.Navigate(url)
diff --git a/contrib/cros_ca_linux/lib/utils/logging.py b/contrib/cros_ca_linux/lib/utils/logging.py
index 2cb4682..d77757b 100644
--- a/contrib/cros_ca_linux/lib/utils/logging.py
+++ b/contrib/cros_ca_linux/lib/utils/logging.py
@@ -25,9 +25,15 @@
 file_handler = logging.FileHandler(LOG_FILE)
 file_handler.setFormatter(formatter)
 
-# Configure the logger
+# Disable the default logging that could be used by other libraries.
+root_logger = logging.getLogger()
+root_logger.addHandler(logging.NullHandler())
+
+# Configure the logger for CrOS CA.
 logger = logging.getLogger("cros_ca")
+logger.propagate = False
 logger.addHandler(stream_handler)
 logger.addHandler(file_handler)
-# TODO: control level from a argument
-logger.setLevel(logging.DEBUG)
+# Default level is INFO. But can be changed by config.
+# See lib.config module.
+logger.setLevel(logging.INFO)