blob: 88c23b0434a4edd2560e43046b517b52dae669c0 [file] [log] [blame]
# 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.
"""The chrome browser based test definition."""
# The following line disable the abstract function implementation warning.
# pylint: disable=W0223
import time
from typing import Union
from selenium import webdriver
from selenium.common import exceptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from lib.testing.lib import base_test
DEFAULT_OPT = webdriver.ChromeOptions()
# TODO: Define the common options for all test.
DEFAULT_OPT.add_argument("--disable-infobars")
DEFAULT_OPT.add_argument("start-maximized")
DEFAULT_OPT.add_argument("--disable-extensions")
DEFAULT_OPT.add_argument("--disable-infobars")
# TODO: allow test to define its own metrics.
HISTOGRAM_MERICS = [
"Graphics.Smoothness.PercentDroppedFrames3.AllSequences",
"EventLatency.KeyPressed.TotalLatency",
"EventLatency.MousePressed.TotalLatency",
"PageLoad.PaintTiming.NavigationToLargestContentfulPaint2",
"PageLoad.PaintTiming.NavigationToFirstContentfulPaint",
"PageLoad.InteractiveTiming.InputDelay3",
"PageLoad.InteractiveTiming.TimeToNextPaint",
# pylint: disable=line-too-long
"PageLoad.Experimental.NavigationTiming.NavigationStartToFirstResponseStart",
# pylint: enable=line-too-long
# "Graphics.Smoothness.Jank.AllSequences",
"Graphics.Smoothness.Jank3.AllSequences",
# "PageLoad.LayoutInstability.CumulativeShiftScore",
# "Browser.MainThreadsCongestion",
# "Browser.MainThreadsCongestion.RunningOnly",
# "Media.DroppedFrameCount",
# "Media.DroppedFrameCount2",
# "Graphics.Smoothness.MaxStale.Video",
# "Graphics.Smoothness.Stale.Video",
]
class ChromeBrowserTest(base_test.BaseTest):
"""Base class for Chrome browser tests using Selenium WebDriver."""
# The test cases can override these values in their class definition.
getHistogram_wait_time = 0.1
tabSwitch_wait_time = 0.1 # Time to wait after switching to new tab.
chromeOps: webdriver.ChromeOptions = DEFAULT_OPT
# TODO: change this to a getter function and check if it is None
# before using.
web_driver: webdriver.Chrome = None
# Histogram metrics to track
histogram_metrics = HISTOGRAM_MERICS
histogram_tabs = {}
def extra_chrome_options(
self, opt: webdriver.ChromeOptions
) -> webdriver.ChromeOptions:
return opt
def get_webdriver(self) -> Union[webdriver.Chrome, webdriver.Remote]:
if not self.web_driver:
opts = self.extra_chrome_options(self.chromeOps)
self.web_driver = self.dut.chrome_webdriver(opts)
return self.web_driver
def run(self):
pass
def _get_histogram_snapshot(
self,
driver: Union[webdriver.Chrome, webdriver.Remote],
histogram: str,
use_delta: bool,
):
result = None
try:
if use_delta:
result = driver.execute_cdp_cmd(
"Browser.getHistogram", {"delta": True, "name": histogram}
)
else:
result = driver.execute_cdp_cmd(
"Browser.getHistogram", {"name": histogram}
)
except exceptions.InvalidArgumentException:
# "Cannot find histogram" error
self.logger.info(
"Browser.getHistogram %s: cannot find histogrqm", histogram
)
except Exception as e:
# Other exceptions.
self.logger.warning(
"Failed to execute Browser.getHistogram %s: %s", histogram, e
)
return result
def open_histogram_tabs(self):
driver = self.web_driver
wait = WebDriverWait(driver, 15)
self.logger.info("Opening histogram tabs...")
for metric in self.histogram_metrics:
driver.switch_to.new_window("tab")
driver.get("chrome://histograms/#" + metric)
wait.until(EC.url_contains(metric))
self.histogram_tabs[metric] = driver.current_window_handle
# capture histogram
histogramSnapshot = self._get_histogram_snapshot(
driver, metric, False
)
time.sleep(self.getHistogram_wait_time)
self.logger.info(
"histogram snapshot (open - full): %s -- %s",
metric,
histogramSnapshot,
)
def enable_histogram_monitoring(self):
driver = self.web_driver
self.logger.info("Enabling histogram monitoring...")
for metric in self.histogram_metrics:
if metric not in self.histogram_tabs:
continue
window_handle = self.histogram_tabs[metric]
driver.switch_to.window(window_handle)
time.sleep(self.tabSwitch_wait_time)
# Switch to monitoring mode
driver.find_element(By.ID, "enable_monitoring").click()
time.sleep(self.getHistogram_wait_time)
self.logger.info("Monitoring mode enabled %s", metric)
histogramSnapshot = self._get_histogram_snapshot(
driver, metric, True
)
time.sleep(self.getHistogram_wait_time)
self.logger.info(
"histogram snapshot (monitor - delta): %s -- %s",
metric,
histogramSnapshot,
)
histogramSnapshot = self._get_histogram_snapshot(
driver, metric, False
)
time.sleep(self.getHistogram_wait_time)
self.logger.info(
"histogram snapshot (monitor - full): %s -- %s",
metric,
histogramSnapshot,
)
def get_histogram_snapshots(self, annotation: str):
driver = self.web_driver
for metric in self.histogram_metrics:
histogramSnapshot = self._get_histogram_snapshot(
driver, metric, True
)
time.sleep(self.getHistogram_wait_time)
self.logger.info(
"histogram snapshot (%s - delta): %s -- %s",
annotation,
metric,
histogramSnapshot,
)
histogramSnapshot = self._get_histogram_snapshot(
driver, metric, False
)
time.sleep(self.getHistogram_wait_time)
self.logger.info(
"histogram snapshot ({annotation} - full): %s -- %s",
metric,
histogramSnapshot,
)
def get_historam_data(self):
histogram_dict = {}
driver = self.web_driver
self.logger.info("Get histogram data.")
if not self.histogram_dir.exists():
self.histogram_dir.mkdir(parents=True)
for metric in self.histogram_metrics:
if metric not in self.histogram_tabs:
continue
window_handle = self.histogram_tabs[metric]
driver.switch_to.window(window_handle)
time.sleep(self.tabSwitch_wait_time)
# capture screenshot before monitoring mode disabled
if not self.save_screenshot(f"stopping_{metric}"):
self.logger.info(
"Failed to save a screenshot on DUT when stopping %s",
metric,
)
time.sleep(0.5)
# capture histogram before monitoring mode disabled
histogramSnapshot = self._get_histogram_snapshot(
driver, metric, True
)
time.sleep(self.getHistogram_wait_time)
self.logger.info(
"histogram snapshot (before monitoring stopped - delta): "
+ "%s -- %s",
metric,
histogramSnapshot,
)
histogramSnapshot = self._get_histogram_snapshot(
driver, metric, False
)
time.sleep(self.getHistogram_wait_time)
self.logger.info(
"histogram snapshot (before monitoring stopped - full): "
+ "%s -- %s",
metric,
histogramSnapshot,
)
# Stop monitoring mode
driver.find_element(By.ID, "stop").click()
time.sleep(0.25)
self.logger.info("monitoring mode stopped: %s", metric)
if not self.save_screenshot(f"stopped_{metric}"):
self.logger.info(
"Failed to save a screenshot on DUT after stopping %s",
metric,
)
time.sleep(0.5)
# capture histogram after monitoring mode disabled
histogramSnapshot = self._get_histogram_snapshot(
driver, metric, True
)
time.sleep(self.getHistogram_wait_time)
self.logger.info(
"histogram snapshot (monitoring stopped - delta): %s -- %s",
metric,
histogramSnapshot,
)
histogramSnapshot = self._get_histogram_snapshot(
driver, metric, False
)
time.sleep(self.getHistogram_wait_time)
self.logger.info(
"histogram snapshot (monitoring stopped - full): %s -- %s",
metric,
histogramSnapshot,
)
# Exand the histograms and capture the samples, mean and the body
histograms = driver.find_element(By.ID, "histograms")
headers = histograms.find_elements(
By.CLASS_NAME, "histogram-header"
)
samples, mean_value = "-1", "-1"
try:
for i, header in enumerate(headers):
histogram_name = header.get_attribute("histogram-name")
# Click the header text to expand the histogram details.
header.find_element(By.CLASS_NAME, "expand").click()
time.sleep(1)
# calculate mean and capture samples size
header_text = header.find_element(
By.CLASS_NAME, "histogram-header-text"
).text
word = header_text.split(" ")
samples, mean_value = word[3], word[7]
self.logger.info(
"%s mean: %s samples: %s",
histogram_name,
mean_value,
samples,
)
self.save_screenshot(f"expend_{metric}")
time.sleep(0.1)
# print histogram body
histogram_body = histograms.find_elements(
By.XPATH, "//div[@class='histogram-body']//p"
)[i]
self.logger.info(
"histogram body: \n%s", histogram_body.text
)
except Exception:
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.info(
"histogram snapshot (get data - full): %s -- %s",
metric,
histogramSnapshot,
)
return histogram_dict
def enable_performance(self):
self.web_driver.execute_cdp_cmd("Performance.enable", {})
def save_screenshot(self, suffix: str) -> bool:
if not self.screenshots_dir.exists():
self.screenshots_dir.mkdir(parents=True)
file = self.screenshots_dir / f"screenshot-{suffix}.png"
return self.web_driver.save_screenshot(file)