src/platform/dev: Devices benchmarking POC code. - Added automation code for use cases scenarios. - Integrated time measurement functionality for benchmarking. - Include CPU and memory measurements for the use cases. - Added agents code for remote end-to-end connection. BUG=none TEST=none Change-Id: I68fb6518bf1fe6ed584beefacff6e635113701db Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/5253151 Reviewed-by: Eric Peers <epeers@google.com> Tested-by: Eric Peers <epeers@google.com> Commit-Queue: Abed Abdulghaffar <abdulghaffar.abed1@exalt.corp-partner.google.com>
diff --git a/contrib/cros_ca/.env b/contrib/cros_ca/.env new file mode 100644 index 0000000..24547b0 --- /dev/null +++ b/contrib/cros_ca/.env
@@ -0,0 +1,3 @@ +WIN_APP_DRIVER_PATH="C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe" +LOGIN_ACCOUNT_EMAIL= +LOGIN_ACCOUNT_PASSWORD= \ No newline at end of file
diff --git a/contrib/cros_ca/README.md b/contrib/cros_ca/README.md new file mode 100644 index 0000000..3f8ee49 --- /dev/null +++ b/contrib/cros_ca/README.md
@@ -0,0 +1,213 @@ +# GoogleBenchmarking + +## Description + +The Google Benchmarking project comprises Python scripts designed to +execute various use cases and scenarios on a computing system. Additionally, +certain +scripts are tailored to conduct measurements, including time measurements, +pertinent to these use cases. This project is compatible with both ChromeOS +and Windows operating systems, ensuring versatility across different +computing environments. + +Furthermore, the automation code within the project supports two major web +browsers for the Windows operating system: Google Chrome and Microsoft Edge. +This enables users to conduct benchmarking and automation tasks seamlessly +across these browsers, enhancing the project's utility and applicability in a +broader range of scenarios. + +## Prerequisites for Windows: + +1. Python and pip installed on the local + machine. [[Python Installation Steps](#python-installation)] + +2. Installation of required Python packages using the provided requirements + file. +3. Installation of required Chrome Driver for Google Chrome browser. + [[Google Chrome + Driver Installation Steps](#chromedriver-installation)] +4. Installation of required Edge Driver for Microsoft Edge browser. + [[Microsoft Edge Driver + Installation Steps](#edgedriver-installation)] +5. For running native apps on Windows we need to have the following: + - Install WinAppDriver for native app + testing. [[WinAppDriver Installation Steps](#winappdriver-installation)] + - Install Microsoft Office\root\office16 that's compatible with the + one in the ```constant.py``` file, for + example: ```C:\Program Files\Microsoft Office\root\Office16\EXCEL.exe``` + - Once native desktop office app installed, open the + applications only for first time, for example Excel, Word, + PowerPoint, and make sure to have ```ScreenTip style``` + option equal to ```Don't show ScreenTips``` by navigate + to File menu -> Options -> General -> ScreenTip style. +  +4. Add the testing email and password that will be shared with you in .env file. + +5. To run the code on Windows. You need to add the project to environment paths: + - create a user variable named ```PYTHONPATH``` with the absolute path of + the project on your local machine as its value. + +### <a id="python-installation"></a>How to install Python and PIP on Windows? + +1. You need to install Python 3.12.0. You can follow the link below for Windows + 64-bit: + ```https://www.python.org/ftp/python/3.12.0/python-3.12.0-amd64.exe``` +2. Run the installer executable file. +3. Select ```Add python.exe to PATH```. + - Choose ```Customize installation``` and ensure that all the options are + selected (the default), then press ```Next```. +4. Select ```Install Python 3.12 for all users``` and then press the + ```Install``` button. +5. Once the installation is complete, press the ```Close``` button. +6. Verify the Python version by executing the command ```python --version``` + in the command prompt; it should return ```Python 3.12.0```. + +### <a id="chromedriver-installation"></a>How to install Google Chrome Driver? + +1. To install the Chrome driver. You can follow the installation link below: + + ```https://googlechromelabs.github.io/chrome-for-testing/``` +2. Install the compatible version with your Google Chrome browser. You can + check your Google Chrome browser by opening the browser: + ```Help -> About Google Chrome``` +3. Extract the compressed file then move the executable file to your Local + Disk (C:) ```C:\chromedriver.exe```. + +### <a id="edgedriver-installation"></a>How to install Microsoft Edge Driver? + +1. To install the Edge driver. You can follow the installation link below: + + ```https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/``` +2. Install the compatible version with your Microsoft Edge browser. You can + check your Microsoft Edge browser by opening the browser: + ```Help and feedback -> About Microsoft Edge``` +3. Extract the compressed file then move the executable file to your Local + Disk (C:) ```C:\msedgedriver.exe```. + +### <a id="winappdriver-installation"></a>How to install and Run WinAppDriver for Windows? + +1. To install the WinAppDriver. You can follow the installation link below: + ```https://github.com/microsoft/WinAppDriver/releases/download/v1.2.1/WindowsApplicationDriver_1.2.1.msi``` +2. Enable Windows Developer mode from settings -> Update & Security -> For + developers -> Developer mode. +3. To execute the native application tests then you need to run the + ```WinAppDriver.exe```. +4. Navigate to the Windows Application Driver directory and double-click the + file ```WinAppDriver.exe``` to run it. + +## Prerequisites for ChromeOS: + +1. Install ChromiumOS testing image and enable developer mode on your device. + You can follow the steps by links below: + - ```https://www.chromium.org/chromium-os/developer-library/guides/development/developer-guide/#installing-chromiumos-on-your-device``` + - ```https://www.chromium.org/chromium-os/developer-library/guides/debugging/debug-buttons/``` +2. Run the following commands from chronos terminal that opened by ctrl + alt + ->: + - sudo /usr/share/vboot/bin/make_dev_ssd.sh --remove_rootfs_verification --force + - reboot +3. Add the following environment variables to ~/.bashrc file: + - note `PYTHONPATH` should have absolute path to the project on your local machine. + ``` + export PATH="$HOME/.local/bin:/usr/bin:$PATH" + export PATH=$PATH:/usr/local/bin + export PYTHONPATH="<absolute path to the project>" + ``` + - execute ```source ~/.bashrc``` after adding them. +4. Verify that default testing image python installed is python3 version + by use this command ```python3 --version``` + and to install pip use this command + ```python3 -m ensurepip``` +5. Install required packages from requirements.txt by this command +```python3 -m pip install -r requirements.txt``` +6. Verify testing image has chromedriver in correct location that in ```/usr/local/chromedriver/chromedriver``` +7. Add testing email and password to .env file +8. Do the following steps: + - (as `root`) Add ```--remote-debugging-port=9222``` at the end of /etc/chrome_dev.conf. + - Reboot + - (as `chronos`) Run ```netstat -al | grep 9222``` and make sure `localhost` is listening. +9. Before run any use case, ensure that all browser extension is disabled and +no applications is opened rather than croshell to run the tests. +## Usage: + +1. Clone or update the chromium code to your local machine. +2. Navigate to the project directory (cros_ca). +3. Install the required packages by running: + ```doctest + pip install -r requirements.txt + ``` +4. Run the automation code from Windows terminal (check the available tests + [[Tests List](#available-tests)]) , + for example: + ```doctest + python automated_use_cases\excel\excel_google_web.py + ``` +5. To Run the web automation code using ```Microsoft Edge``` instead of the + default behavior that will open Google Chrome from Windows terminal: + ```doctest + python automated_use_cases\load_cnn\load_cnn.py edge + ``` +6. Run the automation code from ChromeOS terminal (check the available tests + [[Tests List](#available-tests)]) , + for example: + ```doctest + python3 automated_use_cases/excel/excel_google_web.py + ``` +7. To Run the web automation code using ```Microsoft Edge``` instead of the + default behavior that will open Google Chrome from ChromeOS terminal: + ```doctest + python3 automated_use_cases/load_cnn/load_cnn.py edge + ``` +## <a id="available-tests"></a>Tests List + +We can run a single test for each command, with the test script names provided +in the list below: + +1. Windows and ChromeOS platform: + - browse_four_website.py + - excel_google_web.py + - excel_microsoft_web.py + - load_cnn.py + - load_speed_test.py + - pdf_web.py + - ppt_google_web.py + - ppt_microsoft_web.py + - word_google_web.py + - word_microsoft_web.py + +2. Windows platform: + - excel_native_desktop.py + - ppt_native_desktop.py + - word_native_desktop.py + - windows_photo.py + - windows_video.py + +## Notes + +- The native automation code designed to be run on the Windows devices. +- Ensure that your Google Chrome and Microsoft Edge browsers are updated + before running the code. This will make it easier to find the compatible + webdriver in the recent versions. +- Testing email and password will be same for Google and Microsoft web + use cases. +- When running native automation code, ensure that the application used in + test is closed before running it. +- For Windows Video recording use case, ensure there is no other windows + application is opened on screen, rather than the CMD to run the use case, + to avoid recording other windows application, because it is designed to + record YouTube browser tab. +- In case of using different email and password in .env file, make sure + to use free subscription for MS Office 365, for example having gmail + account to be able to run Google Web use case, and create MS Office 365 + account with it, because Microsoft Web use cases are designed to run on + free subscription. + +## Remaining Enhancements + +1. Create a script that will automate the possible steps from the + prerequisites such as installing python/pip. + +## Code Formatting and Analysis: + +- The project code is formatted using the Black formatter to ensure + consistency and readability. +- Pylint is utilized as a static code analyzer to maintain code quality and + identify potential issues. \ No newline at end of file
diff --git a/contrib/cros_ca/Scripts/InstallPython.ps1 b/contrib/cros_ca/Scripts/InstallPython.ps1 new file mode 100644 index 0000000..6b64e07 --- /dev/null +++ b/contrib/cros_ca/Scripts/InstallPython.ps1
@@ -0,0 +1,52 @@ +# PowerShell Script to Download and Install Python 3.12.0 +# This script automates the process of downloading and installing Python 3.12.0. + +# Check if Python 3.12.0 is already installed +$installedVersion = & python --version 2>&1 +if ($installedVersion -match "Python 3.12.0") { + Write-Host "Python 3.12.0 is already installed. Exiting..." + exit +} + +# Parse Command-Line Arguments +$installForAllUsers = $false +if ($args -contains '--all-users') { + $installForAllUsers = $true +} + +# Check for Admin Privileges +$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") +if (!$isAdmin) { +Write-Host "Installing Python requires administrative privileges. Please run the script as an administrator." +exit +} + + +# Setup variables +$pythonUrl = "https://www.python.org/ftp/python/3.12.0/python-3.12.0-amd64.exe" +$pythonInstaller = "$($env:TEMP)\python.exe" + +# Download Python installer +Write-Host "Downloading Python installer..." +try { + Invoke-WebRequest -Uri $pythonUrl -OutFile $pythonInstaller +} catch { + Write-Host "Failed to download Python installer. Please check your internet connection and try again." + exit 1 +} + +# Install Python +Write-Host "Installing Python..." +try { + $installAllUsersArg = if ($installForAllUsers) { "1" } else { "0" } + Start-Process -FilePath $pythonInstaller -ArgumentList "/quiet InstallAllUsers=$installAllUsersArg PrependPath=1" -Wait +} catch { + Write-Host "Python installation failed:" $_ + exit 1 +} + +# Post-installation cleanup +Write-Host "Cleaning up downloaded files..." +Remove-Item -Path $pythonInstaller -Force + +Write-Host "Python installation completed!" \ No newline at end of file
diff --git a/contrib/cros_ca/Scripts/InstallWinAppDriver.ps1 b/contrib/cros_ca/Scripts/InstallWinAppDriver.ps1 new file mode 100644 index 0000000..e33852b --- /dev/null +++ b/contrib/cros_ca/Scripts/InstallWinAppDriver.ps1
@@ -0,0 +1,50 @@ +# PowerShell Script to Download and Install WindowsApplicationDriver_1.2.1 +# This script automates the process of downloading and installing WindowsApplicationDriver_1.2.1. + +# Check if WindowsApplicationDriver is already installed +$defaultPath = "C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe" +If ((Test-Path $defaultPath)) { + Write-Host "WinAppDriver is already installed. Exiting..." + exit +} + +# Check for Admin Privileges +$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") +if (!$isAdmin) { +Write-Host "Installing WinAppDriver requires administrative privileges. Please run the script as an administrator." +exit +} + + +# Setup variables +$winAppDriverURL = "https://github.com/microsoft/WinAppDriver/releases/download/v1.2.1/WindowsApplicationDriver_1.2.1.msi" +$winAppDriverInstaller = "$($env:TEMP)\WindowsApplicationDriver.msi" + +# Download WinAppDriver installer +Write-Host "Downloading WinAppDriver installer..." +try { + Invoke-WebRequest -Uri $winAppDriverURL -OutFile $winAppDriverInstaller +} catch { + Write-Host "Failed to download WinAppDriver installer. Please check your internet connection and try again." + exit 1 +} + +# Install WinAppDriver +Write-Host "Installing WinAppDriver..." +try { + Start-Process msiexec "/i $winAppDriverInstaller /norestart /qn" -Wait; +} catch { + Write-Host "WinAppDriver installation failed:" $_ + exit 1 +} + +# Post-installation cleanup +Write-Host "Cleaning up downloaded files..." +Remove-Item -Path $winAppDriverInstaller -Force + +Write-Host "WinAppDriver installation completed!" + +# Enable-Developer-Mode +Write-Host "Enabling developer mode..." +reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" +Write-Host "Enabling of developer mode completed!" \ No newline at end of file
diff --git a/contrib/cros_ca/automated_use_cases/__init__.py b/contrib/cros_ca/automated_use_cases/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/browse_four_website/__init__.py b/contrib/cros_ca/automated_use_cases/browse_four_website/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/browse_four_website/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/browse_four_website/browse_four_website.py b/contrib/cros_ca/automated_use_cases/browse_four_website/browse_four_website.py new file mode 100644 index 0000000..4b69b54 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/browse_four_website/browse_four_website.py
@@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Browsing four websites use case.""" +import xml.etree.ElementTree as ET + +from automated_use_cases.common_utility.get_website_util import ( + load_website_by_url, + open_new_blank_tab, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from common.constants import DriversAndAppsPaths, LoggingConfig + +LOGGER = LoggingConfig.get_logger() + +BROWSER_PROCESS_NAME = get_browser_process_name() + + +class BrowseFourWebsiteTest(BenchmarkingTest): + """Browsing four websites use case.""" + + def __init__(self): + self.urls = None + self.xml_file_path = DriversAndAppsPaths.XML_WEBSITES_URL_PATH + + def set_up(self): + """This method will perform preparing of website to be loaded + by reading the XML file and get the websites url.""" + LOGGER.info(f"start set_up method in {self.__class__.__name__}") + tree = ET.parse(self.xml_file_path) + self.urls = tree.getroot() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run the use case for four browsers (Amazon, Google, Bing, + and YouTube). + + This function will perform the following: + 1. Open the Amazon website and wait until the reload sign + in the tab disappears. + 2. Open Google in a new tab and wait until the reload sign + in the tab disappears. + 3. Open Bing in a new tab and wait until the reload sign + in the tab disappears. + 4. Open YouTube in a new tab and wait until the reload sign + in the tab disappears. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + driver.maximize_window() + last_url_index = len(self.urls) - 1 + for index, url in enumerate(self.urls): + load_website_by_url(driver, url.text) + if index != last_url_index: + # open a new tab if not last website to visit + open_new_blank_tab(driver) + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + +if __name__ == "__main__": + BrowseFourWebsiteTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/common_utility/__init__.py b/contrib/cros_ca/automated_use_cases/common_utility/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/common_utility/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/common_utility/account_login.py b/contrib/cros_ca/automated_use_cases/common_utility/account_login.py new file mode 100644 index 0000000..bc9c748 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/common_utility/account_login.py
@@ -0,0 +1,92 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Login to an account before using it.""" + +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.get_website_util import ( + check_complete_page_loading_status, +) +from common.constants import ( + TestingAccountLicense, + PageLoadConfig, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() + + +def login_to_google_account(driver): + """Login to Google testing account. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start login_to_google_account method.") + WebDriverWait(driver, PageLoadConfig.MAX_PAGE_LOAD_TIME).until( + check_complete_page_loading_status() + ) + wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + email_box = wait.until( + EC.visibility_of_element_located((By.ID, "identifierId")) + ) + email_box.send_keys(TestingAccountLicense.LOGIN_ACCOUNT_EMAIL) + wait.until( + EC.visibility_of_element_located((By.ID, "identifierNext")) + ).click() + password_box_xpath = ( + "//input[contains(@autocomplete,'current-password') " + "and contains(@aria-label, 'Enter your password') " + "and contains(@name,'Passwd') " + "and contains(@type,'password')]" + ) + password_box = wait.until( + EC.visibility_of_element_located((By.XPATH, password_box_xpath)) + ) + password_box.send_keys(TestingAccountLicense.LOGIN_ACCOUNT_PASSWORD) + url_before_login = driver.current_url + wait.until( + EC.visibility_of_element_located((By.ID, "passwordNext")) + ).click() + wait.until(EC.url_changes(url_before_login)) + if ( + "passkeyenrollment" in driver.current_url + or "web/chip" in driver.current_url + ): + WebDriverWait(driver, PageLoadConfig.MAX_PAGE_LOAD_TIME).until( + check_complete_page_loading_status() + ) + wait.until( + EC.element_to_be_clickable((By.XPATH, "//span[text()='Not now']")) + ).click() + LOGGER.info("end of login_to_google_account method.") + + +def login_to_microsoft_account(driver): + """Login to Microsoft testing account. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start login_to_microsoft_account method.") + WebDriverWait(driver, PageLoadConfig.MAX_PAGE_LOAD_TIME).until( + check_complete_page_loading_status() + ) + wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + email_box = wait.until( + EC.visibility_of_element_located((By.NAME, "loginfmt")) + ) + email_box.send_keys(TestingAccountLicense.LOGIN_ACCOUNT_EMAIL) + wait.until(EC.visibility_of_element_located((By.ID, "idSIButton9"))).click() + password_box = wait.until( + EC.visibility_of_element_located((By.NAME, "passwd")) + ) + password_box.send_keys(TestingAccountLicense.LOGIN_ACCOUNT_PASSWORD) + wait.until(EC.visibility_of_element_located((By.ID, "idSIButton9"))).click() + wait.until( + EC.visibility_of_element_located((By.ID, "acceptButton")) + ).click() + LOGGER.info("end of login_to_microsoft_account method.")
diff --git a/contrib/cros_ca/automated_use_cases/common_utility/doc_editing_utils.py b/contrib/cros_ca/automated_use_cases/common_utility/doc_editing_utils.py new file mode 100644 index 0000000..6bd8370 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/common_utility/doc_editing_utils.py
@@ -0,0 +1,185 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Script contains some utils function responsible to handle +some documents use cases. +""" +import os +import subprocess +import time + +from selenium.common.exceptions import InvalidArgumentException +from selenium.webdriver import ActionChains +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.account_login import ( + login_to_google_account, + login_to_microsoft_account, +) +from automated_use_cases.common_utility.get_website_util import ( + check_complete_page_loading_status, +) +from benchmarking.benchmarking_exception import BenchmarkingException +from common.constants import ( + PageLoadConfig, + DocumentAccountType, + TestingAccountLicense, + LoggingConfig, + DriversAndAppsPaths, +) + +LOGGER = LoggingConfig.get_logger() + + +def open_and_load_account_document_or_drive( + driver, url_path: str, account_type: DocumentAccountType +): + """Helper function to open web Excel, Word and PowerPoint documents + or open account drive using Google or Microsoft account. + + Args: + driver: The WebDriver instance used to interact with the browser. + url_path: The URL of web document. + account_type: The account type to use for login, either + 'google' or 'microsoft'. + + Returns: + tuple: A tuple contains login time. + + Raises: + InvalidArgumentException: If the provided app_account is not + 'google' or 'microsoft'. + """ + LOGGER.info( + "start open_and_load_account_document_or_drive method with " + f"url_path = {url_path} and " + f"account_type = {account_type}." + ) + if driver is not None and account_type is not None and url_path: + driver.maximize_window() + validate_email_and_password_existence() + driver.get(url_path) + if account_type == DocumentAccountType.GOOGLE: + time_before_login = time.time() + login_to_google_account(driver) + elif account_type == DocumentAccountType.MICROSOFT: + time_before_login = time.time() + login_to_microsoft_account(driver) + else: + LOGGER.info( + "open_and_load_account_document_or_drive failed " + "with InvalidArgumentException." + ) + raise InvalidArgumentException( + "Error! Invalid doc_account_type parameter, " + "you can only use 'GOOGLE' or 'MICROSOFT'" + ) + login_time = time.time() - time_before_login + WebDriverWait(driver, PageLoadConfig.MAX_PAGE_LOAD_TIME).until( + check_complete_page_loading_status() + ) + LOGGER.info("end of open_and_load_account_document_or_drive method.") + return login_time + LOGGER.info( + "open_and_load_account_document_or_drive failed " + "with BenchmarkingException." + ) + raise BenchmarkingException( + "passed parameters should not be None or empty." + ) + + +def validate_email_and_password_existence(): + """Function to validate if email and password used in login process + exist or not. + + Raises: + EnvironmentError: in case email or password not exist in + environment variables. + """ + LOGGER.info("start validate_email_and_password_existence method.") + if ( + not TestingAccountLicense.LOGIN_ACCOUNT_EMAIL + or not TestingAccountLicense.LOGIN_ACCOUNT_PASSWORD + ): + LOGGER.info( + "validate_email_and_password_existence failed " + "with EnvironmentError." + ) + raise EnvironmentError( + "Error! Cannot find the account email or password," + " please check your environment variables." + ) + LOGGER.info("end of validate_email_and_password_existence method.") + + +def select_sheet_content(driver, sheet_content): + """Helper function to select sheet content. + + Args: + driver: The WebDriver instance used to interact with the browser. + sheet_content: Indicate the sheet content web element + to interact with it. + """ + LOGGER.info("start select_sheet_content method.") + actions = ActionChains(driver) + ( + actions.move_to_element(sheet_content) + .click(sheet_content) + .key_down(Keys.CONTROL) + .send_keys(Keys.HOME) + .send_keys("a") + .key_up(Keys.CONTROL) + .perform() + ) + LOGGER.info("end of select_sheet_content method.") + + +def close_file_picker_dialog(driver): + """Function used to close file picker dialog when upload files + functionality required. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + driver.execute_script( + "HTMLInputElement.prototype.click = function () {" + + " if (this.type !== 'file') {" + + " HTMLElement.prototype.click.call(this);" + + " }" + + " else if (!this.parentNode) {" + + " this.style.display = 'none';" + + " this.ownerDocument.documentElement.appendChild(this);" + + " this.addEventListener('change', () => this.remove());" + + " }" + + "}" + ) + + +def open_win_app_driver(): + """Function to open WinAppDriver application. + + Raises: + EnvironmentError: in case WinAppDriver path in environment file + is empty or not exist in the system. + """ + if not DriversAndAppsPaths.WIN_APP_DRIVER_PATH: + raise EnvironmentError( + "Error! WinAppDriver path is empty," + " please check your environment variables." + ) + if not os.path.exists(DriversAndAppsPaths.WIN_APP_DRIVER_PATH): + raise EnvironmentError( + "Error! WinAppDriver path not exist in the system," + " please check your environment variables." + ) + os.startfile(DriversAndAppsPaths.WIN_APP_DRIVER_PATH) + time.sleep(5) + + +def close_win_app_driver(): + """Function to close WinAppDriver application.""" + time.sleep(5) + subprocess.call("TASKKILL /F /IM WinAppDriver.exe")
diff --git a/contrib/cros_ca/automated_use_cases/common_utility/get_website_util.py b/contrib/cros_ca/automated_use_cases/common_utility/get_website_util.py new file mode 100644 index 0000000..d493737 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/common_utility/get_website_util.py
@@ -0,0 +1,56 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Script use to visit the websites based on driver type.""" + +from selenium.webdriver.support.wait import WebDriverWait + +from benchmarking.benchmarking_exception import BenchmarkingException +from benchmarking.time_benchmark import time_measure +from common.constants import PageLoadConfig, LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +@time_measure() +def open_new_blank_tab(driver): + """Function to open a new tab and switch to it. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start open_new_blank_tab method.") + driver.execute_script("window.open('');") + driver.switch_to.window(driver.window_handles[-1]) + LOGGER.info("end of open_new_blank_tab method.") + + +@time_measure(1) +def load_website_by_url(driver, url): + """Function used to load any website by its url + and wait until the website loading finish. + + Args: + driver: The WebDriver instance used to interact with the browser. + url: The URL for website needs to be loaded. + """ + LOGGER.info(f"start load_website_by_url method with url = {url}.") + if url and driver is not None: + driver.get(url) + WebDriverWait(driver, PageLoadConfig.MAX_PAGE_LOAD_TIME).until( + check_complete_page_loading_status() + ) + LOGGER.info("end of load_website_by_url method.") + else: + LOGGER.info("load_website_by_url failed with BenchmarkingException.") + raise BenchmarkingException("passed parameters should not be None.") + + +def check_complete_page_loading_status(): + """Helper function to check if the page load is complete.""" + LOGGER.info("executing check_complete_page_loading_status method.") + return ( + lambda driver: driver.execute_script("return document.readyState") + == "complete" + )
diff --git a/contrib/cros_ca/automated_use_cases/common_utility/google_documents_utils.py b/contrib/cros_ca/automated_use_cases/common_utility/google_documents_utils.py new file mode 100644 index 0000000..4a5ed32 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/common_utility/google_documents_utils.py
@@ -0,0 +1,341 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Script contains some functions to handle common cases in +Google documents. +""" +import time + +from selenium.common.exceptions import ( + NoSuchElementException, + TimeoutException, +) +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + close_file_picker_dialog, +) +from automated_use_cases.common_utility.get_website_util import ( + check_complete_page_loading_status, +) +from benchmarking.benchmarking_exception import BenchmarkingException +from benchmarking.time_benchmark import time_measure +from common.constants import PageLoadConfig, LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +def wait_visibility_of_document_docs_bars(driver, wait): + """Function to wait visibility of document and close doc material + dialog when it is available. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start wait_visibility_of_document_docs_bars method.") + wait.until(EC.visibility_of_element_located((By.ID, "docs-bars"))) + try: + driver.find_element_by_xpath( + "//span[@class='docs-material-gm-dialog-title-close']" + ).click() + LOGGER.info("docs material dialog closed.") + except NoSuchElementException: + pass + try: + driver.find_element_by_xpath("//span[text()='Got it']").click() + LOGGER.info("docs material dialog closed.") + except NoSuchElementException: + pass + LOGGER.info("end of wait_visibility_of_document_docs_bars method.") + + +def check_view_mode_if_open_needed(driver): + """Function to check if view mode needs to be open. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start check_view_mode_if_open_needed method.") + view_mode = driver.find_element_by_id("viewModeButton") + if "Show the menus" in view_mode.get_attribute("aria-label"): + view_mode.click() + LOGGER.info("end of check_view_mode_if_open_needed method.") + + +def wait_saving_status(wait): + """Function to wait saving status of documents to be saved. + + Args: + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start wait_saving_status method.") + saving_status_path = ( + "//div[contains(@id," + "'docs-save-indicator-badge') and contains" + "(@aria-label,'Document status: Saving….')]" + ) + wait.until_not( + EC.visibility_of_element_located((By.XPATH, saving_status_path)) + ) + LOGGER.info("end of wait_saving_status method.") + + +@time_measure() +def add_comment(driver, wait, index_of_comment_button: int): + """Function to add comments in document. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + index_of_comment_button: Index of comment button from + select menu appears when its appear. + """ + LOGGER.info( + "start add_comment method with " + f"index_of_comment_button = {index_of_comment_button}." + ) + wait.until(EC.visibility_of_element_located((By.ID, "docs-insert-menu"))) + driver.find_element_by_id("docs-insert-menu").click() + # select add comment from insert menu + insert_menu_xpath = ( + "//div[contains(@class,'goog-menu " + "goog-menu-vertical docs-material ia-menu " + "ia-primary-menu ia-has-icon " + "apps-menu-hide-mnemonics') and " + "contains(@style, 'visibility: visible;')]" + ) + wait.until(EC.visibility_of_element_located((By.XPATH, insert_menu_xpath))) + insert_menu = driver.find_element_by_xpath(insert_menu_xpath) + add_comments_button = insert_menu.find_elements_by_class_name( + "goog-menuitem-content" + )[index_of_comment_button] + add_comments_button.click() + comment_input_xpath = ( + "//div[contains(@class,'editable " + "awr-inlineAssistMonitorContainer')" + " and contains(@placeholder, " + "'Comment or add others with @')]" + ) + wait.until( + EC.visibility_of_element_located((By.XPATH, comment_input_xpath)) + ) + comment_input = driver.find_element_by_xpath(comment_input_xpath) + comment_input.click() + comment_input.send_keys("Hi.") + post_comment_button_xpath = ( + "//div[contains(@class," + "'goog-inline-block jfk-button " + "jfk-button-action docos-input-post " + "docos-input-buttons-post') and " + "contains(@aria-disabled, 'false')]" + ) + wait.until( + EC.visibility_of_element_located((By.XPATH, post_comment_button_xpath)) + ) + post_comment_button = driver.find_element_by_xpath( + post_comment_button_xpath + ) + post_comment_button.click() + wait_saving_status(wait) + LOGGER.info("end of add_comment method.") + + +def move_to_main_container(driver, wait): + """Helper function for moving to main container. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + main_container_xpath = "//div[@role='main']" + main_container = wait.until( + EC.visibility_of_element_located((By.XPATH, main_container_xpath)) + ) + action = ActionChains(driver) + action.move_to_element(main_container).click(main_container).perform() + + +def close_first_usage_dialogs(driver): + """Function used to close some dialogs window that's appear for first + usage of Google account with Google Drive. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start close_first_usage_dialogs method.") + try: + WebDriverWait(driver, 12).until( + EC.element_to_be_clickable( + (By.XPATH, "//button[@aria-label='Close']") + ) + ).click() + except TimeoutException: + pass + try: + hide_details_button_xpath = ( + "//div[(@role='button') and (@aria-label='Hide Details')]" + ) + WebDriverWait(driver, 2).until( + EC.element_to_be_clickable((By.XPATH, hide_details_button_xpath)) + ).click() + except TimeoutException: + pass + LOGGER.info("end of close_first_usage_dialogs method.") + + +def upload_document_to_google_drive(driver, wait, file_path, new_doc_name): + """Function used for uploading office document to Google Drive. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + file_path: The absolute path of file that needs to be uploaded + from local storge. + new_doc_name: The new name of document that be represented by uuid4 + with file type extension. + + Returns: + the url of uploaded document. + """ + LOGGER.info( + "start upload_document_to_google_drive method with " + f"file_path = {file_path} and new_doc_name = {new_doc_name}" + ) + close_first_usage_dialogs(driver) + wait.until( + EC.visibility_of_element_located((By.XPATH, "//span[text()='New']")) + ).click() + close_file_picker_dialog(driver) + wait.until( + EC.visibility_of_element_located( + (By.XPATH, "//div[(@role='menuitem') and (@aria-posinset='5')]") + ) + ) + wait.until( + EC.element_to_be_clickable((By.XPATH, "//div[text()='File upload']")) + ).click() + upload_options = wait.until( + EC.presence_of_element_located((By.XPATH, "//input[@type='file']")) + ) + upload_options.send_keys(file_path) + wait.until( + EC.visibility_of_element_located( + (By.XPATH, "//span[text()='1 upload complete']") + ) + ) + LOGGER.info(f"File with name = {new_doc_name}, uploaded successfully.") + window_before = driver.window_handles + move_to_main_container(driver, wait) + last_height = 0 + document_xpath = f"//div[(text() = '{new_doc_name}')]" + while True: + document_list = driver.find_elements_by_xpath(document_xpath) + if len(document_list) > 0: + document = document_list[0] + actions = ActionChains(driver) + actions.move_to_element(document).double_click(document).perform() + break + action = ActionChains(driver) + action.send_keys(Keys.PAGE_DOWN).perform() + time.sleep(2) + new_height = driver.find_element_by_xpath( + "//c-wiz[@role='grid']" + ).location["y"] + if new_height == last_height: + LOGGER.info( + "end of upload_document_to_google_drive method with " + "no document found." + ) + raise BenchmarkingException( + f"Cannot find uploaded file = {new_doc_name} in files list" + ) + last_height = new_height + wait.until(EC.new_window_is_opened(window_before)) + driver.switch_to.window(driver.window_handles[-1]) + WebDriverWait(driver, PageLoadConfig.MAX_PAGE_LOAD_TIME).until( + check_complete_page_loading_status() + ) + wait_visibility_of_document_docs_bars(driver, wait) + time.sleep(10) + LOGGER.info("end of upload_document_to_google_drive method.") + return driver.current_url + + +def delete_created_document_from_google_drive(driver, wait, new_doc_name): + """Function used to delete the document that was created in setup + operation from Google Drive. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + new_doc_name: Name of new document was copied from original + document, and it's equal random uuid. + """ + LOGGER.info( + "start delete_created_document_from_google_drive method with " + f"new_doc_name = {new_doc_name}." + ) + close_first_usage_dialogs(driver) + move_to_main_container(driver, wait) + last_height = 0 + doc_to_delete_xpath = f"//div[(text() = '{new_doc_name}')]" + move_to_trash_button_xpath = ( + "//div[(@role='button') and (@aria-label='Move to trash')]" + ) + while True: + doc_to_delete_list = driver.find_elements_by_xpath(doc_to_delete_xpath) + if len(doc_to_delete_list) > 0: + doc_to_delete = doc_to_delete_list[0] + action = ActionChains(driver) + action.move_to_element(doc_to_delete).click(doc_to_delete).perform() + wait.until( + EC.element_to_be_clickable( + (By.XPATH, move_to_trash_button_xpath) + ) + ).click() + break + action = ActionChains(driver) + action.send_keys(Keys.PAGE_DOWN).perform() + time.sleep(2) + new_height = driver.find_element_by_xpath( + "//c-wiz[@role='grid']" + ).location["y"] + if new_height == last_height: + LOGGER.info( + "end of delete_created_document_from_google_drive method with " + "no document to delete found." + ) + return + last_height = new_height + wait.until( + EC.invisibility_of_element_located((By.XPATH, doc_to_delete_xpath)) + ) + driver.find_element_by_xpath( + "//span[contains(@aria-label, 'Trashed items')]" + ).click() + empty_trash_button = wait.until( + EC.visibility_of_element_located( + (By.XPATH, "//button[contains(@aria-label, 'Empty trash')]") + ) + ) + driver.execute_script("arguments[0].click();", empty_trash_button) + delete_forever_button = wait.until( + EC.visibility_of_element_located( + (By.XPATH, "//span[(text() = 'Delete forever')]") + ) + ) + driver.execute_script("arguments[0].click();", delete_forever_button) + time.sleep(10) + LOGGER.info("end of delete_created_document_from_google_drive method.")
diff --git a/contrib/cros_ca/automated_use_cases/common_utility/microsoft_documents_utils.py b/contrib/cros_ca/automated_use_cases/common_utility/microsoft_documents_utils.py new file mode 100644 index 0000000..329ce05 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/common_utility/microsoft_documents_utils.py
@@ -0,0 +1,386 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Script contains some functions to handle common cases in +Microsoft documents. +""" +import time + +from selenium.common.exceptions import ( + NoSuchElementException, + TimeoutException, +) +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + close_file_picker_dialog, +) +from automated_use_cases.common_utility.get_website_util import ( + check_complete_page_loading_status, +) +from benchmarking.benchmarking_exception import BenchmarkingException +from common.constants import PageLoadConfig, LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +def switch_to_native_app_window(driver, wait, no_of_windows_to_be=1): + """Function used to switch to native application window after open it. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + no_of_windows_to_be: Number of window that should be available + when switch between windows required + """ + LOGGER.info( + "start switch_to_native_app_window method" + f" with no_of_windows_to_be = {no_of_windows_to_be}." + ) + wait.until(EC.number_of_windows_to_be(no_of_windows_to_be)) + driver.switch_to.window(driver.window_handles[0]) + driver.maximize_window() + LOGGER.info("end switch_to_native_app_window method.") + + +def close_native_app_document_without_save(driver): + """Function used to close native application without save the document + by select don't save option. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + """ + LOGGER.info("start close_native_app_document_without_save method.") + frame_helper_buttons = driver.find_elements_by_class_name( + "NetUIAppFrameHelper" + ) + for i in frame_helper_buttons: + if i.text == "Close": + i.click() + all_win_handle = driver.window_handles + driver.switch_to.window(all_win_handle[0]) + driver.find_element_by_name("Don't Save").click() + LOGGER.info("end of close_native_app_document_without_save method.") + + +def wait_saving_status(wait): + """Function to wait saving status of documents to be saved. + + Args: + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start wait_saving_status method.") + saving_status_path = ( + "//div[contains(@aria-label,'Saving... Last saved: Just now')]" + ) + wait.until_not( + EC.visibility_of_element_located((By.XPATH, saving_status_path)) + ) + LOGGER.info("end of wait_saving_status method.") + + +def check_ribbon_container_if_open_needed(driver): + """Function to check if ribbon container needs to be open. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start check_ribbon_container_if_open_needed method.") + ribbon_container = driver.find_element_by_id("RibbonContainer") + if not ribbon_container.is_displayed(): + header_region = driver.find_element_by_id("HeaderRegion") + action = ActionChains(driver) + action.move_to_element(header_region).perform() + LOGGER.info("ribbon container open done.") + LOGGER.info("end of check_ribbon_container_if_open_needed method.") + + +def switch_to_document_frame(driver, wait, frame_name): + """Function to switch to document frame. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + frame_name: Name of frame needs to switch to it. + """ + LOGGER.info( + f"start switch_to_document_frame method with frame_name = {frame_name}." + ) + frame = wait.until(lambda d: d.find_elements_by_id(frame_name))[0] + driver.switch_to.frame(frame) + wait.until(EC.visibility_of_element_located((By.ID, "AppBrand"))) + LOGGER.info("end of switch_to_document_frame method.") + + +def insert_comment_on_native_app_document(driver, wait): + """Helper function to insert a comment for native application document. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start insert_comment_on_native_app_document method.") + driver.find_element_by_accessibility_id("TabInsert").click() + wait.until( + EC.visibility_of_element_located((By.CLASS_NAME, "NetUIRibbonButton")) + ) + driver.find_element_by_accessibility_id("InsertNewComment").click() + all_win_handle = driver.window_handles + driver.switch_to.window(all_win_handle[0]) + LOGGER.info("end of insert_comment_on_native_app_document method.") + + +def write_comment_on_native_app_document(driver, wait): + """Helper function to write the comment for native application document. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start write_comment_on_native_app_document method.") + wait.until( + EC.visibility_of_element_located((By.NAME, "Start a conversation")) + ) + comments_input_section = driver.find_element_by_name("Start a conversation") + comments_input_section.click() + comments_input_section.send_keys("Hi.") + wait.until( + EC.visibility_of_element_located( + (By.NAME, "Post comment (Ctrl + Enter)") + ) + ).click() + LOGGER.info("end of write_comment_on_native_app_document method.") + + +def upload_document_to_one_drive( + driver, wait, new_doc_name, frame_name, file_path +): + """Function used for uploading office document to One Drive. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + new_doc_name: The new name of document that be represented by uuid4 + with file type extension. + frame_name: Name of office document frame used to switch to it. + file_path: The absolute path of file that needs to be uploaded + from local storge. + Returns: + The url of uploaded document. + + Raises: + BenchmarkingException: in case uploaded file not found in files list. + """ + LOGGER.info( + "start upload_document_to_one_drive method with " + f"new_doc_name = {new_doc_name} and frame_name = {frame_name} " + f"and file_path = {file_path}" + ) + close_first_usage_dialogs(driver) + wait.until(EC.visibility_of_element_located((By.NAME, "My files"))).click() + add_new_button = wait.until( + EC.visibility_of_element_located( + (By.XPATH, "//button[(@title='Add new')]") + ) + ) + close_file_picker_dialog(driver) + while True: + upload_button = driver.find_elements_by_xpath( + "//button[@title='Files upload']" + ) + if len(upload_button) > 0: + driver.execute_script("arguments[0].click();", upload_button[0]) + break + driver.execute_script("arguments[0].click();", add_new_button) + time.sleep(2) + + upload_file_input = wait.until( + EC.presence_of_element_located((By.XPATH, "//input[@type='file']")) + ) + upload_file_input.send_keys(file_path) + wait.until( + EC.visibility_of_element_located( + (By.XPATH, "//div[starts-with(text(),'Uploaded ')]") + ) + ) + LOGGER.info(f"File with name = {new_doc_name}, uploaded successfully.") + no_window_before = driver.window_handles + move_to_main_app_content(driver, wait) + last_height = 0 + document_xpath = f"//button[text() = '{new_doc_name}']" + while True: + document_list = driver.find_elements_by_xpath(document_xpath) + if len(document_list) > 0: + document = document_list[0] + driver.execute_script( + "return arguments[0].scrollIntoView(true);", document + ) + driver.execute_script("arguments[0].click();", document) + break + action = ActionChains(driver) + action.send_keys(Keys.PAGE_DOWN).perform() + time.sleep(2) + new_height = driver.find_element_by_class_name( + "ms-SelectionZone" + ).location["y"] + if new_height == last_height: + LOGGER.info( + "end of upload_document_to_one_drive method with " + "no document found." + ) + raise BenchmarkingException( + f"Cannot find uploaded file = {new_doc_name} in files list" + ) + last_height = new_height + wait.until(EC.new_window_is_opened(no_window_before)) + driver.switch_to.window(driver.window_handles[-1]) + WebDriverWait(driver, PageLoadConfig.MAX_PAGE_LOAD_TIME).until( + check_complete_page_loading_status() + ) + time.sleep(10) + switch_to_document_frame(driver, wait, frame_name) + LOGGER.info("end of upload_document_to_one_drive method.") + return driver.current_url + + +def close_first_usage_dialogs(driver): + """Function used to close some dialogs window that's appear for first + usage of Google account with Google Drive. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start close_first_usage_dialogs method.") + try: + dialog1_xpath = "//button[@aria-label='Close dialog']" + WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, dialog1_xpath)) + ).click() + except TimeoutException: + pass + try: + dialog2_xpath = "//button[@aria-label='Close']" + WebDriverWait(driver, 4).until( + EC.element_to_be_clickable((By.XPATH, dialog2_xpath)) + ).click() + except TimeoutException: + pass + LOGGER.info("end of close_first_usage_dialogs method.") + + +def move_to_main_app_content(driver, wait): + """Helper function for moving to main app content. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + main_app_content_xpath = ( + "//main[contains(@data-automationid, 'main-app-content')]" + ) + main_app_content = wait.until( + EC.visibility_of_element_located((By.XPATH, main_app_content_xpath)) + ) + action = ActionChains(driver) + action.move_to_element(main_app_content).click(main_app_content).perform() + + +def delete_created_document_from_one_drive(driver, wait, new_doc_name): + """Function used to delete the document that was created in setup + operation from One Drive. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + new_doc_name: Name of new document was copied from original + document, and it's equal random uuid. + """ + LOGGER.info( + "start delete_created_document_from_one_drive method with " + f"new_doc_name = {new_doc_name}." + ) + close_first_usage_dialogs(driver) + wait.until(EC.visibility_of_element_located((By.NAME, "My files"))).click() + move_to_main_app_content(driver, wait) + doc_to_be_deleted_xpath = f"//button[starts-with(text(), '{new_doc_name}')]" + last_height = 0 + while True: + doc_to_be_deleted_list = driver.find_elements_by_xpath( + doc_to_be_deleted_xpath + ) + if len(doc_to_be_deleted_list) > 0: + doc_to_be_deleted = doc_to_be_deleted_list[0] + action = ActionChains(driver) + action.move_to_element(doc_to_be_deleted).context_click( + doc_to_be_deleted + ).perform() + break + action = ActionChains(driver) + action.send_keys(Keys.PAGE_DOWN).perform() + time.sleep(2) + new_height = driver.find_element_by_class_name( + "ms-SelectionZone" + ).location["y"] + if new_height == last_height: + LOGGER.info( + "end of delete_created_document_from_one_drive method with " + "no document to delete found." + ) + return + last_height = new_height + wait.until(EC.visibility_of_element_located((By.NAME, "Move to"))).click() + wait.until( + EC.frame_to_be_available_and_switch_to_it( + ( + By.XPATH, + "//iframe[starts-with(@data-automationid, 'filePickerFrame')]", + ) + ) + ) + wait.until(EC.element_to_be_clickable((By.NAME, "Move here"))).click() + driver.switch_to.parent_frame() + try: + time.sleep(5) + driver.find_element_by_name("Move anyway").click() + except NoSuchElementException: + pass + time.sleep(5) + wait.until(EC.visibility_of_element_located((By.NAME, "Delete"))).click() + wait.until( + EC.visibility_of_element_located( + (By.XPATH, "//button[(@data-automationid= 'confirmbutton')]") + ) + ).click() + wait.until( + EC.invisibility_of_element_located((By.XPATH, doc_to_be_deleted_xpath)) + ) + wait.until( + EC.visibility_of_element_located((By.NAME, "Recycle bin")) + ).click() + wait.until( + EC.visibility_of_element_located((By.NAME, "Empty recycle bin")) + ).click() + confirm_empty_trash = wait.until( + EC.element_to_be_clickable((By.XPATH, "//span[(text() = 'Yes')]")) + ) + driver.execute_script("arguments[0].click();", confirm_empty_trash) + wait.until( + EC.invisibility_of_element_located((By.NAME, "Empty recycle bin")) + ) + time.sleep(10) + LOGGER.info("end of delete_created_document_from_one_drive method.")
diff --git a/contrib/cros_ca/automated_use_cases/excel/__init__.py b/contrib/cros_ca/automated_use_cases/excel/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/excel/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/excel/excel_google_web.py b/contrib/cros_ca/automated_use_cases/excel/excel_google_web.py new file mode 100644 index 0000000..a5d137c --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/excel/excel_google_web.py
@@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Google Sheet web use case.""" +import uuid + +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_and_load_account_document_or_drive, +) +from automated_use_cases.common_utility.google_documents_utils import ( + wait_visibility_of_document_docs_bars, + check_view_mode_if_open_needed, + delete_created_document_from_google_drive, + upload_document_to_google_drive, +) +from automated_use_cases.excel.utils.excel_google_utils import ( + select_and_start_from_first_page, + select_sheet_content, + create_stacked_bar_chart, + delete_stacked_bar_chart, + create_pivot_table_in_new_sheet, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from common.common_utility import get_file_path_to_upload +from common.constants import ( + UseCasesURLS, + PageLoadConfig, + DocumentAccountType, + HumanDelay, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() + +BROWSER_PROCESS_NAME = get_browser_process_name() + + +class ExcelGoogleWebTest(BenchmarkingTest): + """Test class for Google Sheet web use case.""" + + def __init__(self): + self.new_doc_url = None + self.doc_extension_type = ".xlsx" + self.new_doc_name = str(uuid.uuid4()) + self.doc_extension_type + self.doc_account_type = DocumentAccountType.GOOGLE + self.account_drive_url = UseCasesURLS.GOOGLE_DRIVE_URL_PATH + self.excel_file_path = get_file_path_to_upload( + self.new_doc_name, self.doc_extension_type + ) + + def set_up(self): + LOGGER.info(f"start set_up method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the set_up of use case for Excel Google Sheet. + + This function will perform the following: + 1. Open the testing Google Drive using the browser. + 2. Wait until the login page finish reloading then login + to the Google account. + 3. Create new sheet document. + 4. Add dummy data to sheet document. + 5. Change sheet document name to be equal uuid. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + sheet_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + self.new_doc_url = upload_document_to_google_drive( + driver, sheet_wait, self.excel_file_path, self.new_doc_name + ) + + run() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run the use case for Excel Google Sheet. + + This function will perform the following: + 1. Open the testing Google Sheet using the browser. + 2. Wait until the login page finish reloading then login + to the Google account. + 3. Wait until testing Google sheet page finish reloading. + 4. Select and start from first working sheet + 5. Select the Google Sheet elements and create a Stacked Bar Graph. + 6. Delete the created graph. + 7. Create a Pivot Table in a new sheet with the data and + wait for the save status to be saved. + 8. Delete existing new sheets except first working sheet and wait + for the save status to be saved. + 9. Close the browser. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + + Returns: + dict: Dictionary containing variables to be used in + measurements process. + """ + sheet_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + login_time = open_and_load_account_document_or_drive( + driver, self.new_doc_url, self.doc_account_type + ) + wait_visibility_of_document_docs_bars(driver, sheet_wait) + select_and_start_from_first_page(driver, sheet_wait) + select_sheet_content(driver, sheet_wait) + check_view_mode_if_open_needed(driver) + create_stacked_bar_chart(driver, sheet_wait) + delete_stacked_bar_chart(driver, sheet_wait) + create_pivot_table_in_new_sheet(driver, sheet_wait) + return { + "period_of_login_time": login_time, + "human_delay": HumanDelay.GOOGLE_EXCEL_WEB, + } + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + def teardown(self): + LOGGER.info(f"start teardown method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the teardown of use case for Excel Google Sheet. + + This function will perform the following: + 1. Open the testing Google Drive using the browser. + 2. Wait until the login page finish reloading then login + to the Google account. + 3. delete the copy of original sheet document from Google Drive. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + sheet_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + delete_created_document_from_google_drive( + driver, sheet_wait, self.new_doc_name + ) + + run() + LOGGER.info(f"end of teardown method in {self.__class__.__name__}") + + +if __name__ == "__main__": + ExcelGoogleWebTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/excel/excel_microsoft_web.py b/contrib/cros_ca/automated_use_cases/excel/excel_microsoft_web.py new file mode 100644 index 0000000..9a51c72 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/excel/excel_microsoft_web.py
@@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Microsoft Excel web use case.""" +import uuid + +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_and_load_account_document_or_drive, +) +from automated_use_cases.common_utility.microsoft_documents_utils import ( + switch_to_document_frame, + check_ribbon_container_if_open_needed, + delete_created_document_from_one_drive, + upload_document_to_one_drive, +) +from automated_use_cases.excel.utils.excel_microsoft_utils import ( + select_and_start_from_first_page, + select_sheet_content, + create_stacked_bar_chart, + delete_stacked_bar_chart, + create_pivot_table_with_new_sheet, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from common.common_utility import get_file_path_to_upload +from common.constants import ( + UseCasesURLS, + PageLoadConfig, + DocumentAccountType, + HumanDelay, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() + +BROWSER_PROCESS_NAME = get_browser_process_name() + + +class ExcelMSWebTest(BenchmarkingTest): + """Test class for Microsoft Excel web use case.""" + + def __init__(self): + self.new_doc_url = None + self.doc_extension_type = ".xlsx" + self.new_doc_name = str(uuid.uuid4()) + self.doc_extension_type + self.frame_name = "WacFrame_Excel_0" + self.doc_account_type = DocumentAccountType.MICROSOFT + self.account_drive_url = UseCasesURLS.MS_DRIVE_URL_PATH + self.excel_file_path = get_file_path_to_upload( + self.new_doc_name, self.doc_extension_type + ) + + def set_up(self): + LOGGER.info(f"start set_up method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the set_up of use case for Excel web Microsoft Sheet. + + This function will perform the following: + 1. Open the testing One Drive using the browser. + 2. Wait until the login page finish reloading then login + to the Microsoft account. + 3. Create new Excel document. + 4. Add dummy data to Excel document. + 5. Change Excel document name to be equal uuid. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + excel_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + self.new_doc_url = upload_document_to_one_drive( + driver, + excel_wait, + self.new_doc_name, + self.frame_name, + self.excel_file_path, + ) + + run() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run the use case for Excel web Microsoft Sheet. + + This function will perform the following: + 1. Open the testing Microsoft Sheet using the browser. + 2. Wait until the login page finish reloading then login to + the Microsoft account. + 3. Wait until testing Microsoft sheet page finish reloading. + 4. Select and start from first working sheet + 5. Select the Microsoft Sheet elements and create a Stacked + Bar Graph. + 6. Delete the created graph. + 7. Create a Pivot Table in a new sheet with the data + and wait for the + save status to be saved. + 8. Delete existing new sheets except first working sheet and wait + for the save status to be saved. + 9. Close the browser. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + + Returns: + dict: Dictionary containing variables to be used in + measurements process. + """ + excel_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + login_time = open_and_load_account_document_or_drive( + driver, self.new_doc_url, self.doc_account_type + ) + + switch_to_document_frame(driver, excel_wait, self.frame_name) + select_and_start_from_first_page(driver, excel_wait) + select_sheet_content(driver, excel_wait) + check_ribbon_container_if_open_needed(driver) + create_stacked_bar_chart(driver, excel_wait) + delete_stacked_bar_chart(driver, excel_wait) + create_pivot_table_with_new_sheet(driver, excel_wait) + return { + "period_of_login_time": login_time, + "human_delay": HumanDelay.MICROSOFT_EXCEL_WEB, + } + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + def teardown(self): + LOGGER.info(f"start teardown method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the teardown of use case for Excel web Microsoft Sheet. + + This function will perform the following: + 1. Open the testing Microsoft One Drive using the browser. + 2. Wait until the login page finish reloading then login to + the Microsoft account. + 3. delete the copy of original sheet document from One Drive. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + excel_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + delete_created_document_from_one_drive( + driver, excel_wait, self.new_doc_name + ) + + run() + LOGGER.info(f"end of teardown method in {self.__class__.__name__}") + + +if __name__ == "__main__": + ExcelMSWebTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/excel/excel_native_desktop.py b/contrib/cros_ca/automated_use_cases/excel/excel_native_desktop.py new file mode 100644 index 0000000..2b5c85a --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/excel/excel_native_desktop.py
@@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Microsoft Excel native desktop use case.""" + +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_win_app_driver, + close_win_app_driver, +) +from automated_use_cases.common_utility.microsoft_documents_utils import ( + close_native_app_document_without_save, + switch_to_native_app_window, +) +from automated_use_cases.excel.utils.excel_native_desktop_utils import ( + select_sheet_content, + create_stacked_bar_chart, + delete_stacked_bar_chart, + create_pivot_table, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup +from common.constants import ProcessNames, PageLoadConfig, LoggingConfig + +LOGGER = LoggingConfig.get_logger() + +PROCESS_NAME = (ProcessNames.EXCEL,) + + +class ExcelNativeDesktopTest(BenchmarkingTest): + """Test class for Microsoft Excel native desktop use case.""" + + def set_up(self): + LOGGER.info(f"start set_up method in {self.__class__.__name__}") + open_win_app_driver() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=PROCESS_NAME) + @setup(process_name=PROCESS_NAME, is_execution_part=True) + def run(driver): + """Run the use case for Excel native desktop Microsoft Sheet. + + This function will perform the following: + 1. Open the testing Microsoft Sheet using the native Excel app. + 2. Wait until testing Microsoft Excel be visible. + 3. Select the Microsoft Sheet elements and create a Stacked Bar + Graph. + 4. Delete the created graph. + 5. Create a Pivot Table in a new sheet with the data. + 6. Close the app and select don't save option. + + This function is decorated with the @setup decorator, + configuring the driver for the native desktop app processes. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + """ + wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + switch_to_native_app_window(driver, wait) + select_sheet_content(driver, wait) + create_stacked_bar_chart(driver, wait) + delete_stacked_bar_chart(driver, wait) + create_pivot_table(driver, wait) + close_native_app_document_without_save(driver) + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + def teardown(self): + LOGGER.info(f"start teardown method in {self.__class__.__name__}") + close_win_app_driver() + LOGGER.info(f"end of teardown method in {self.__class__.__name__}") + + +if __name__ == "__main__": + ExcelNativeDesktopTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/excel/utils/__init__.py b/contrib/cros_ca/automated_use_cases/excel/utils/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/excel/utils/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/excel/utils/excel_google_utils.py b/contrib/cros_ca/automated_use_cases/excel/utils/excel_google_utils.py new file mode 100644 index 0000000..92e76bb --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/excel/utils/excel_google_utils.py
@@ -0,0 +1,238 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Google Sheet web use case utils.""" + +import time + +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC + +from automated_use_cases.common_utility import doc_editing_utils +from automated_use_cases.common_utility.google_documents_utils import ( + wait_saving_status, +) +from benchmarking.benchmarking_exception import BenchmarkingException +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +def get_columns_menu(driver): + """Get the columns menu elements from the Google Sheet page. + + Args: + driver: The WebDriver instance used to interact with the browser. + + Returns: + list: A list of column menu elements. + + Raises: + BenchmarkingException: in case no columns found. + """ + LOGGER.info("starts get_columns_menu method.") + columns_menu = driver.find_elements_by_xpath( + "//div[contains(@class,'waffle-dbsource-column-filtered-menu') and" + " contains(@style, 'visibility: visible;')]" + ) + for i in columns_menu: + if "display: none;" not in i.get_attribute("style"): + LOGGER.info("end of get_columns_menu method.") + return i.find_elements_by_class_name( + "waffle-filterable-by-text-contains-menu-item-label" + ) + LOGGER.info("get_columns_menu failed with BenchmarkingException.") + raise BenchmarkingException( + "Error in get_columns_menu for the excel, no columns found" + ) + + +def select_and_start_from_first_page(driver, wait): + """Helper function to select an element and start from the first working + sheet. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start select_and_start_from_first_page method.") + wait.until_not( + EC.visibility_of_element_located( + (By.ID, "waffle-loading-progress-bar-overlay") + ) + ) + wait.until( + EC.visibility_of_element_located( + ( + By.XPATH, + "//div[contains(@class,'docs-sheet-container-bar " + "goog-toolbar goog-inline-block')]", + ) + ) + ) + working_sheet_xpath = ( + "//span[contains" + "(@class,'docs-sheet-tab-name')" + " and (text()='Sheet1')]" + ) + wait.until( + EC.visibility_of_element_located((By.XPATH, working_sheet_xpath)) + ) + first_page = driver.find_element_by_xpath(working_sheet_xpath) + first_page.click() + LOGGER.info("ending of select_and_start_from_first_page method.") + + +def select_sheet_content(driver, wait): + """Helper function to select all the elements in the Excel sheet (Ctrl+a). + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start select_sheet_content method.") + grid_wrapper = "grid-scrollable-wrapper" + wait.until(EC.visibility_of_element_located((By.CLASS_NAME, grid_wrapper))) + sheet_content = driver.find_element_by_class_name(grid_wrapper) + doc_editing_utils.select_sheet_content(driver, sheet_content) + LOGGER.info("end of select_sheet_content method.") + + +def get_insert_menu_xpath(): + """Get the XPath of the insert choice in the Excel taskbar. + + Returns: + String represents insert menu xpath. + """ + LOGGER.info("executing get_insert_menu_xpath method.") + return ( + "//div[contains(@class,'docs-material ia-menu" + " ia-primary-menu ia-has-icon " + "apps-menu-hide-mnemonics') and " + "contains(@style, 'visibility: visible;')]" + ) + + +def delete_stacked_bar_chart(driver, wait): + """Helper function to delete the stacked bar chart that we create it before. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start delete_stacked_bar_chart method.") + stacked_chart_path = ( + "//div[contains(@class," + "'docs-charts-editor-charttype-option') and " + "contains(@data-tooltip," + " 'Stacked bar chart')]" + ) + wait.until(EC.visibility_of_element_located((By.XPATH, stacked_chart_path))) + driver.find_element_by_xpath(stacked_chart_path).click() + actions = ActionChains(driver) + actions.send_keys(Keys.DELETE) + actions.perform() + LOGGER.info("end of delete_stacked_bar_chart method.") + + +@time_measure() +def create_stacked_bar_chart(driver, wait): + """Helper function to Create a stacked bar from the existing values in + the Excel sheet. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start create_stacked_bar_chart method.") + wait.until(EC.visibility_of_element_located((By.ID, "docs-insert-menu"))) + driver.find_element_by_id("docs-insert-menu").click() + + # Get insert choice xpath + insert_menu_xpath = get_insert_menu_xpath() + + wait.until(EC.visibility_of_element_located((By.XPATH, insert_menu_xpath))) + insert_menu = driver.find_element_by_xpath(insert_menu_xpath) + chart_button = insert_menu.find_elements_by_class_name( + "goog-menuitem-content" + )[4] + chart_button.click() + wait.until( + EC.visibility_of_element_located( + ( + By.CLASS_NAME, + "waffle-borderless-embedded-object-container", + ) + ) + ) + wait.until( + EC.visibility_of_element_located( + (By.CLASS_NAME, "docs-charts-editor-charttype-select-name") + ) + ) + driver.find_element_by_class_name( + "docs-charts-editor-charttype-select-name" + ).click() + LOGGER.info("end of create_stacked_bar_chart method.") + + +@time_measure() +def create_pivot_table_in_new_sheet(driver, wait): + """Helper function to create a pivot table for the Excel. + + This function will create the pivot table and select the required rows + and values for this pivot table. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start create_pivot_table_in_new_sheet method.") + insert_menu_xpath = get_insert_menu_xpath() + driver.find_element_by_id("docs-insert-menu").click() + wait.until(EC.visibility_of_element_located((By.XPATH, insert_menu_xpath))) + insert_menu = driver.find_element_by_xpath(insert_menu_xpath) + pivot_button = insert_menu.find_elements_by_class_name( + "goog-menuitem-content" + )[5] + pivot_button.click() + wait.until( + EC.visibility_of_element_located( + (By.CLASS_NAME, "jfk-radiobutton-radio") + ) + ) + driver.find_element_by_class_name("jfk-radiobutton-radio").click() + driver.find_element_by_xpath( + "//div[contains(@class," + "'docs-material-gm-dialog-call-to-action-button')]" + ).click() + wait.until( + EC.presence_of_element_located( + (By.CLASS_NAME, "waffle-sidebar-content") + ) + ) + wait.until( + EC.visibility_of_element_located((By.ID, "waffle-pivot-sidebar")) + ) + + # start selecting the pivot item + add_row = driver.find_element_by_id("waffle-pivot-add-field-row") + add_value = driver.find_element_by_id("waffle-pivot-add-field-value") + counter = 0 + while counter < 2: + time.sleep(1) + add_row.click() + menus = get_columns_menu(driver) + menus[0].click() + counter += 1 + time.sleep(1) + add_value.click() + menus = get_columns_menu(driver) + menus[2].click() + time.sleep(1) + wait_saving_status(wait) + LOGGER.info("end of create_pivot_table_in_new_sheet method.")
diff --git a/contrib/cros_ca/automated_use_cases/excel/utils/excel_microsoft_utils.py b/contrib/cros_ca/automated_use_cases/excel/utils/excel_microsoft_utils.py new file mode 100644 index 0000000..046f97e --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/excel/utils/excel_microsoft_utils.py
@@ -0,0 +1,177 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Microsoft Excel web use case utils.""" + +import time + +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC + +from automated_use_cases.common_utility import doc_editing_utils +from automated_use_cases.common_utility.microsoft_documents_utils import ( + wait_saving_status, +) +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +def select_and_start_from_first_page(driver, wait): + """Helper function to select an element and start from the first working + sheet. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start select_and_start_from_first_page method.") + wait.until( + EC.visibility_of_element_located((By.CLASS_NAME, "ewa-stb-tabs")) + ) + working_sheet_xpath = ( + "//a[contains(@class,'tab-anchor-text') and" + " contains(@role, 'tab') " + "and (@aria-label='Sheet1')]" + ) + wait.until( + EC.visibility_of_element_located((By.XPATH, working_sheet_xpath)) + ) + first_page = driver.find_element_by_xpath(working_sheet_xpath) + first_page.click() + LOGGER.info("end of select_and_start_from_first_page method.") + + +def select_sheet_content(driver, wait): + """Helper function to select all the elements in the Excel sheet (Ctrl+a). + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start select_sheet_content method.") + sheet_content = "m_excelWebRenderer_ewaCtl_sheetContentDiv_Flow_0" + wait.until(EC.visibility_of_element_located((By.ID, sheet_content))) + sheet_content = driver.find_element_by_id(sheet_content) + doc_editing_utils.select_sheet_content(driver, sheet_content) + LOGGER.info("end of select_sheet_content method.") + + +@time_measure() +def create_stacked_bar_chart(driver, wait): + """Helper function to Create a stacked bar from the existing values in + the Excel sheet. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start create_stacked_bar_chart method.") + wait.until(EC.visibility_of_element_located((By.ID, "Insert"))) + driver.find_element_by_id("Insert").click() + # select pivot table from bar + wait.until( + EC.visibility_of_element_located( + (By.ID, "InsertChart-previewExpandButton") + ) + ) + driver.find_element_by_id("InsertChart-previewExpandButton").click() + wait.until( + EC.visibility_of_element_located( + (By.ID, "Ribbon-InsertChart-previewExpandButtonDropdown") + ) + ) + wait.until( + EC.visibility_of_element_located( + ( + By.ID, + "title-InsertChartGallery-Bar", + ) + ) + ) + wait.until( + EC.presence_of_element_located((By.ID, "Insert2DBarChartStacked")) + ) + wait.until( + EC.visibility_of_element_located((By.ID, "Insert2DBarChartStacked")) + ) + wait.until(EC.element_to_be_clickable((By.ID, "Insert2DBarChartStacked"))) + driver.find_element_by_id("Insert2DBarChartStacked").click() + LOGGER.info("end of create_stacked_bar_chart method.") + + +def delete_stacked_bar_chart(driver, wait): + """Helper function to delete the stacked bar chart that we create it before. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start delete_stacked_bar_chart method.") + wait.until( + EC.visibility_of_element_located((By.CLASS_NAME, "ewa-fo-img-div")) + ) + actions = ActionChains(driver) + actions.send_keys(Keys.DELETE) + actions.perform() + LOGGER.info("end of delete_stacked_bar_chart method.") + + +@time_measure() +def create_pivot_table_with_new_sheet(driver, wait): + """Helper function to create a pivot table for the Excel. + + This function will create the pivot table and select the required rows + and values for this pivot table. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: The WebDriverWait instance used for waiting on conditions. + """ + LOGGER.info("start create_pivot_table_with_new_sheet method.") + driver.find_element_by_id("Insert").click() + # select pivot table from bar + wait.until( + EC.visibility_of_element_located( + (By.XPATH, '//*[@id="InsertPivotTableMenu"]/button[1]') + ) + ) + driver.find_element_by_xpath( + '//*[@id="InsertPivotTableMenu"]/button[1]' + ).click() + + pivot_frame_xpath = "//iframe[contains(@id,'.Insights.Https.Bundle')]" + pivot_option_element = wait.until( + lambda d: d.find_elements_by_id("newWorksheet") + or d.find_elements_by_xpath(pivot_frame_xpath) + )[0] + if pivot_option_element.tag_name == "iframe": + driver.switch_to.frame(pivot_option_element) + wait.until(EC.visibility_of_element_located((By.ID, "recommendation0"))) + driver.find_element_by_xpath( + '//*[@id="recommendation0_InsertOnNew"]/span/span[1]' + ).click() + driver.switch_to.parent_frame() + else: + wait.until(EC.visibility_of_element_located((By.ID, "newWorksheet"))) + driver.find_element_by_id("newWorksheet").click() + driver.find_element_by_id("WACDialogActionButton").click() + + # wait Pane of selection to appear + wait.until(EC.visibility_of_element_located((By.ID, "FarPane"))) + wait.until( + EC.visibility_of_element_located((By.CLASS_NAME, "ms-List-cell")) + ) + pivot_elements = driver.find_elements_by_xpath( + "//div[starts-with(@class,'checkbox-checkbox checkbox')]" + ) + for i in range(3): + time.sleep(1) + pivot_elements[i].click() + time.sleep(1) + wait_saving_status(wait) + LOGGER.info("end of create_pivot_table_with_new_sheet method.")
diff --git a/contrib/cros_ca/automated_use_cases/excel/utils/excel_native_desktop_utils.py b/contrib/cros_ca/automated_use_cases/excel/utils/excel_native_desktop_utils.py new file mode 100644 index 0000000..58d0a26 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/excel/utils/excel_native_desktop_utils.py
@@ -0,0 +1,117 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Microsoft Excel native desktop use case utils.""" + +import time + +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC + +from automated_use_cases.common_utility import doc_editing_utils +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +def select_sheet_content(driver, wait): + """Function to select sheet content. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start select_sheet_content method.") + sheet_grid = "XLSpreadsheetGrid" + wait.until(EC.visibility_of_element_located((By.CLASS_NAME, sheet_grid))) + sheet_content = driver.find_element_by_class_name(sheet_grid) + doc_editing_utils.select_sheet_content(driver, sheet_content) + LOGGER.info("end of select_sheet_content method.") + + +@time_measure() +def create_stacked_bar_chart(driver, wait): + """Function to create stacked bar chart. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start create_stacked_bar_chart method.") + # insert + driver.find_element_by_accessibility_id("TabInsert").click() + wait.until(EC.visibility_of_element_located((By.CLASS_NAME, "NetUIAnchor"))) + driver.find_element_by_accessibility_id( + "ChartTypeColumnInsertGallery" + ).click() + wait.until( + EC.visibility_of_all_elements_located( + (By.CLASS_NAME, "NetUIGalleryButton") + ) + ) + driver.find_element_by_name("Stacked Bar").click() + LOGGER.info("end of create_stacked_bar_chart method.") + + +def delete_stacked_bar_chart(driver, wait): + """Function to delete stacked bar chart. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start delete_stacked_bar_chart method.") + wait.until(EC.visibility_of_element_located((By.NAME, "Chart Area"))) + actions = ActionChains(driver) + actions.send_keys(Keys.DELETE) + actions.perform() + LOGGER.info("end of delete_stacked_bar_chart method.") + + +@time_measure() +def create_pivot_table(driver, wait): + """Function to create pivot table. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start create_pivot_table method.") + # insert + driver.find_element_by_accessibility_id("TabInsert").click() + # select pivot table from bar + wait.until( + EC.visibility_of_element_located((By.CLASS_NAME, "NetUIRibbonButton")) + ) + driver.find_element_by_name("PivotTable").click() + # wait new page + current_window_handle = driver.current_window_handle + wait.until(EC.number_of_windows_to_be(2)) + all_win_handle = driver.window_handles + driver.switch_to.window(all_win_handle[0]) + wait.until(EC.visibility_of_element_located((By.NAME, "New Worksheet"))) + driver.find_element_by_name("New Worksheet").click() + # press ok + driver.find_element_by_name("OK").click() + ### + driver.switch_to.window(current_window_handle) + wait.until( + EC.visibility_of_element_located((By.CLASS_NAME, "NetUInetpane")) + ) + pivot_elements = driver.find_elements_by_tag_name("CheckBox") + for i in range(3): + time.sleep(1) + pivot_elements[i].click() + time.sleep(1) + LOGGER.info("end of create_pivot_table method.")
diff --git a/contrib/cros_ca/automated_use_cases/load_cnn/__init__.py b/contrib/cros_ca/automated_use_cases/load_cnn/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/load_cnn/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/load_cnn/load_cnn.py b/contrib/cros_ca/automated_use_cases/load_cnn/load_cnn.py new file mode 100644 index 0000000..a8ec020 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/load_cnn/load_cnn.py
@@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""CNN use case.""" + +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.get_website_util import ( + load_website_by_url, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig, PageLoadConfig + +LOGGER = LoggingConfig.get_logger() + +BROWSER_PROCESS_NAME = get_browser_process_name() + + +def handle_cookies_popup(driver): + """Function to handle cookies popup by rejection. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start handle_cookies_popup method.") + try: + show_purposes = driver.find_element(By.ID, "onetrust-pc-btn-handler") + show_purposes.click() + reject_cookies = driver.find_element( + By.XPATH, + '//*[@id="onetrust-pc-sdk"]/div/div[3]/div[1]/button[1]', + ) + reject_cookies.click() + LOGGER.info("end of handle_cookies_popup method.") + except NoSuchElementException: + LOGGER.info("end of handle_cookies_popup method.") + + +@time_measure() +def watch_cnn_video(driver): + """Function to select and start watching a video. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start watch_cnn_video method.") + video = driver.find_element( + By.XPATH, '//*[@id="pageHeader"]/div/div/div[1]/div[2]/a[1]' + ) + video.click() + WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT).until( + EC.presence_of_element_located((By.ID, "pauseIconTitle")) + ) + LOGGER.info("end of watch_cnn_video method.") + + +class LoadCNNTest(BenchmarkingTest): + """Test class for CNN use case.""" + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run the use case for CNN page. + + This function will perform the following: + 1. Open the CNN edition page and wait until the reload sign + disappears in the tab. + 2. Open the video page from the CNN page. + 3. Wait until the video start playing. + 4. Close the browser. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + driver.maximize_window() + load_website_by_url(driver, "https://edition.cnn.com") + handle_cookies_popup(driver) + watch_cnn_video(driver) + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + +if __name__ == "__main__": + LoadCNNTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/load_speed_test/__init__.py b/contrib/cros_ca/automated_use_cases/load_speed_test/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/load_speed_test/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/load_speed_test/load_speed_test.py b/contrib/cros_ca/automated_use_cases/load_speed_test/load_speed_test.py new file mode 100644 index 0000000..ec6d122 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/load_speed_test/load_speed_test.py
@@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""SpeedTest use case.""" +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.get_website_util import ( + load_website_by_url, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from common.constants import LoggingConfig, PageLoadConfig + +LOGGER = LoggingConfig.get_logger() +RESULTS_LOGGER = LoggingConfig.get_logger(False) +BROWSER_PROCESS_NAME = get_browser_process_name() + + +def handle_cookies_popup(driver): + """Function to handle cookies popup by rejection. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start handle_cookies_popup method.") + try: + driver.find_element_by_xpath("//button[text()='Reject All']").click() + except NoSuchElementException: + pass + LOGGER.info("end of handle_cookies_popup method.") + + +def start_speed_test(driver, wait): + """Function to start speed test and wait the result. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start executing start_speed_test method.") + wait.until( + EC.element_to_be_clickable((By.XPATH, "//span[text()='Go']")) + ).click() + wait.until( + EC.visibility_of_element_located( + ( + By.XPATH, + "//div[contains(@class, 'result-container-speed-active')]", + ) + ) + ) + download_speed = driver.find_element_by_xpath( + "//span[contains(@class, 'download-speed')]" + ).get_attribute("innerHTML") + upload_speed = driver.find_element_by_xpath( + "//span[contains(@class, 'upload-speed')]" + ).get_attribute("innerHTML") + ping_speed = driver.find_element_by_xpath( + "//span[contains(@class, 'ping-speed')]" + ).get_attribute("innerHTML") + RESULTS_LOGGER.info(f"download_speed = {download_speed}") + RESULTS_LOGGER.info(f"upload_speed = {upload_speed}") + RESULTS_LOGGER.info(f"ping_speed = {ping_speed}") + LOGGER.info("end of executing start_speed_test method.") + + +class LoadSpeedTestTest(BenchmarkingTest): + """Test class for SpeedTest use case.""" + + def __init__(self): + self.speed_test_url = "https://www.speedtest.net/" + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run the use case for SpeedTest. + + This function will perform the following: + 1. Open and load speed test website. + 2. Wait until the login page finish reloading. + 3. Handle Cookies Popup. + 4. Start speed test and wait the result. + 5. Close the browser. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + driver.maximize_window() + load_website_by_url(driver, self.speed_test_url) + handle_cookies_popup(driver) + start_speed_test(driver, wait) + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + +if __name__ == "__main__": + LoadSpeedTestTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/pdf/__init__.py b/contrib/cros_ca/automated_use_cases/pdf/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/pdf/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/pdf/pdf_web.py b/contrib/cros_ca/automated_use_cases/pdf/pdf_web.py new file mode 100644 index 0000000..ed60f1d --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/pdf/pdf_web.py
@@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Load PDF document from local storge use case.""" + +from msedge.selenium_tools import Edge +from selenium import webdriver as chrome_webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.get_website_util import ( + load_website_by_url, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from benchmarking.time_benchmark import time_measure +from common.constants import UseCasesURLS, LoggingConfig + +LOGGER = LoggingConfig.get_logger() + +BROWSER_PROCESS_NAME = get_browser_process_name() + + +@time_measure() +def print_and_wait_until_loading_preview_finish(driver): + """Function to press on print and wait until loading preview finish. + + Args: + driver: The WebDriver instance used to interact with the browser. + """ + LOGGER.info("start print_and_wait_until_loading_preview_finish method.") + wait = WebDriverWait(driver, 20) + window_before = driver.window_handles + driver.execute_script("window.print();") + wait.until(EC.new_window_is_opened(window_before)) + driver.switch_to.window(driver.window_handles[-1]) + # wait until pages loaded in print mode + if isinstance(driver, chrome_webdriver.Chrome): + wait.until_not( + EC.text_to_be_present_in_element( + (By.XPATH, "/html/body"), "Loading preview..." + ) + ) + elif isinstance(driver, Edge): + wait.until_not( + EC.visibility_of_element_located( + (By.XPATH, "//div[contains(@role,'progressbar')]") + ) + ) + LOGGER.info("end of print_and_wait_until_loading_preview_finish method.") + + +class PDFWebTest(BenchmarkingTest): + """Test class for Load PDF document from local storge use case.""" + + def __init__(self): + self.doc_url = UseCasesURLS.PDF_WEB_URL_PATH + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run the Web PDF use case. + + This function will perform the following: + 1. Open browser and load PDF file from local storge. + 2. Click on the printer button. + 3. Wait until the loading preview step finish. + 4. Close the browser. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + driver.maximize_window() + load_website_by_url(driver, self.doc_url) + print_and_wait_until_loading_preview_finish(driver) + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + +if __name__ == "__main__": + PDFWebTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/photo_editing/__init__.py b/contrib/cros_ca/automated_use_cases/photo_editing/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/photo_editing/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/photo_editing/windows_photo.py b/contrib/cros_ca/automated_use_cases/photo_editing/windows_photo.py new file mode 100644 index 0000000..b27c19c --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/photo_editing/windows_photo.py
@@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Windows Photos Editing/Crop native desktop use case.""" +import os + +from selenium.common.exceptions import NoSuchElementException, TimeoutException +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_win_app_driver, + close_win_app_driver, +) +from automated_use_cases.common_utility.microsoft_documents_utils import ( + switch_to_native_app_window, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup +from benchmarking.time_benchmark import time_measure +from common.constants import ( + ProcessNames, + PageLoadConfig, + DriversAndAppsPaths, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() + +PROCESS_NAME = (ProcessNames.PHOTOS_SERVICE, ProcessNames.PHOTOS_APP) + + +def close_syncing_with_one_drive_tips(driver): + """Helper Function to close syncing with one drive tips appear + when opens Photos app for first usage. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + """ + LOGGER.info("start close_syncing_with_one_drive_tips method.") + try: + driver.find_element_by_name("Next").click() + driver.find_element_by_name("Go to Photos").click() + WebDriverWait(driver, 5).until( + lambda d: d.find_elements_by_accessibility_id( + "AlternateCloseButton" + ) + )[0].click() + except (NoSuchElementException, TimeoutException): + pass + LOGGER.info("end of close_syncing_with_one_drive_tips method.") + + +@time_measure() +def select_test_photo(driver, wait, photo_name): + """Function to select the test photo and open it. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + photo_name: photo name to be selected and opened. + """ + LOGGER.info( + "start select_test_photo method with " f"photo_name = {photo_name}." + ) + photo = wait.until(EC.visibility_of_element_located((By.NAME, photo_name))) + actions = ActionChains(driver) + actions.move_to_element(photo).click(photo).send_keys(Keys.ENTER).perform() + LOGGER.info("end of select_test_photo method.") + + +@time_measure() +def edit_crop_save_test_photo(driver, wait, new_photo_name): + """Function used to edit, crop and save the test photo. + + This Function will perform the following: + 1. Edit the photo. + 2. Cut the photo by square aspect ratio. + 3. Rotate clockwise the test photo four times, + 4. Flip horizontally the test photo. + 5. Save the test photo on same photo. + 6. Wait until new edited photo to be saved. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + new_photo_name: The new name of edited photo that will be saved. + """ + LOGGER.info( + "start edit_crop_save_test_photo method with " + f"new_photo_name = {new_photo_name}." + ) + # edit the image and make a cut for it by Square aspect ratio + wait.until( + EC.visibility_of_element_located((By.NAME, "Edit image")) + ).click() + switch_to_native_app_window(driver, wait, 2) + wait.until( + EC.visibility_of_element_located((By.NAME, "Free aspect ratio")) + ).click() + wait.until( + EC.visibility_of_element_located((By.NAME, "Square aspect ratio")) + ).click() + wait.until(EC.visibility_of_element_located((By.NAME, "Done"))).click() + # rotate the image in clockwise direction 4 times + counter = 0 + while counter < 4: + wait.until( + EC.element_to_be_clickable((By.NAME, "Rotate clockwise")) + ).click() + counter += 1 + # flip the image horizontally + wait.until( + EC.visibility_of_element_located((By.NAME, "Flip image horizontally")) + ).click() + # save the image by save as button + wait.until( + EC.visibility_of_element_located( + (By.NAME, "Press Enter for more save options") + ) + ).click() + wait.until( + EC.visibility_of_element_located((By.NAME, "Save as copy")) + ).click() + file_name_input = wait.until( + EC.visibility_of_element_located((By.NAME, "File name:")) + ) + actions = ActionChains(driver) + actions.move_to_element(file_name_input).send_keys(new_photo_name).perform() + wait.until(EC.element_to_be_clickable((By.NAME, "Save"))).click() + # wait image to be saved before close the windows + wait.until(EC.visibility_of_element_located((By.NAME, "Edit image"))) + LOGGER.info("end of edit_crop_save_test_photo method.") + + +def close_current_window(driver, original_window_handle): + """Function to close current window before close original window. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + original_window_handle: Original or first window opened in Photos + app to be closed after switching to it. + """ + LOGGER.info( + "start close_current_window method with " + f"original_window_handle = {original_window_handle}." + ) + driver.close() + driver.switch_to.window(original_window_handle) + LOGGER.info("end of close_current_window method.") + + +def check_resource_folder_existence(driver, wait, photo_dir_path): + """Function to check if native_app_files folder exist in Photos app list. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + photo_dir_path: Path of photo directory. + """ + LOGGER.info( + "start check_resource_folder_existence method with " + f"photo_dir_path = {photo_dir_path}." + ) + native_resource_folder = driver.find_elements_by_name("native_app_files") + if len(native_resource_folder) > 0: + native_resource_folder[0].click() + else: + wait.until( + EC.visibility_of_element_located((By.NAME, "Folders")) + ).click() + driver.find_element_by_accessibility_id( + "AddFolderTemplateButton" + ).click() + folder_input = wait.until( + EC.visibility_of_element_located((By.NAME, "Folder:")) + ) + actions = ActionChains(driver) + actions.move_to_element(folder_input).send_keys( + photo_dir_path + ).perform() + driver.find_element_by_accessibility_id("1").click() + wait.until( + (EC.visibility_of_element_located((By.NAME, "native_app_files"))) + ).click() + LOGGER.info("end of check_resource_folder_existence method.") + + +class WindowsPhotosNativeDesktopTest(BenchmarkingTest): + """Test class for Windows Photos Editing/Crop native desktop use case.""" + + def __init__(self): + self.photo_dir_path = os.path.abspath( + DriversAndAppsPaths.TEST_PHOTO_URL_DIR_PATH + ) + self.photo_name = DriversAndAppsPaths.TEST_PHOTO_NAME + self.new_photo_name = "New" + self.photo_name + self.new_photo_path = os.path.abspath( + self.photo_dir_path + rf"\{self.new_photo_name}" + ) + + def set_up(self): + """This method will perform coping the test photo to Pictures + directory path in Windows platform.""" + LOGGER.info(f"starting set_up method in {self.__class__.__name__}") + open_win_app_driver() + LOGGER.info(f"ending of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=PROCESS_NAME) + @setup(process_name=PROCESS_NAME, is_execution_part=True) + def run(driver): + """Run Windows Photos Editing/Crop native desktop use case. + + This function will perform the following: + 1. Open Windows Photos using the native Photos app. + 2. Select and open the test photo. + 4. Switch to the new test photo window. + 5. Edit, crop and save the new edited test photo in new file. + 6. Close current window before close original window. + + This function is decorated with the @setup decorator, + configuring the driver for the native desktop app processes. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + """ + wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + switch_to_native_app_window(driver, wait) + close_syncing_with_one_drive_tips(driver) + check_resource_folder_existence(driver, wait, self.photo_dir_path) + select_test_photo(driver, wait, self.photo_name) + original_window_handle = driver.current_window_handle + switch_to_native_app_window(driver, wait, 2) + edit_crop_save_test_photo(driver, wait, self.new_photo_path) + close_current_window(driver, original_window_handle) + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + def teardown(self): + """This method will perform delete for test photos from Pictures + directory path in Windows platform.""" + LOGGER.info(f"starting teardown method in {self.__class__.__name__}") + close_win_app_driver() + os.remove(self.new_photo_path) + LOGGER.info(f"ending of teardown method in {self.__class__.__name__}") + + +if __name__ == "__main__": + WindowsPhotosNativeDesktopTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/power_point/__init__.py b/contrib/cros_ca/automated_use_cases/power_point/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/power_point/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/power_point/ppt_google_web.py b/contrib/cros_ca/automated_use_cases/power_point/ppt_google_web.py new file mode 100644 index 0000000..fb63cce --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/power_point/ppt_google_web.py
@@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Google Slides Web use case.""" +import uuid + +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_and_load_account_document_or_drive, +) +from automated_use_cases.common_utility.google_documents_utils import ( + wait_visibility_of_document_docs_bars, + check_view_mode_if_open_needed, + add_comment, + delete_created_document_from_google_drive, + upload_document_to_google_drive, +) +from automated_use_cases.power_point.utils.ppt_google_utils import ( + navigate_to_last_slide, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from common.common_utility import get_file_path_to_upload +from common.constants import ( + UseCasesURLS, + PageLoadConfig, + DocumentAccountType, + HumanDelay, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() + +BROWSER_PROCESS_NAME = get_browser_process_name() + + +class PowerPointGoogleWebTest(BenchmarkingTest): + """Test class for Google Slides Web use case.""" + + def __init__(self): + self.new_doc_url = None + self.doc_extension_type = ".pptx" + self.new_doc_name = str(uuid.uuid4()) + self.doc_extension_type + self.doc_account_type = DocumentAccountType.GOOGLE + self.account_drive_url = UseCasesURLS.GOOGLE_DRIVE_URL_PATH + self.ppt_file_path = get_file_path_to_upload( + self.new_doc_name, self.doc_extension_type + ) + + def set_up(self): + LOGGER.info(f"start set_up method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the set_up of use case for Google Slides. + + This function will perform the following: + 1. Open the testing Google Drive using the browser. + 2. Wait until the login page finish reloading then login + to the Google account. + 3. Create new slide document. + 4. Add dummy data to slide document. + 5. Change slide document name to be equal uuid. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + slide_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + self.new_doc_url = upload_document_to_google_drive( + driver, slide_wait, self.ppt_file_path, self.new_doc_name + ) + + run() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run Google Slides use case. + + This function will perform the following: + 1. Open the testing Google Slides using the browser. + 2. Wait until the login page finish reloading then login to + the Google account. + 3. Wait until testing Google Slides page finish reloading. + 4. Navigate to the last slide. + 5. Add comment on the last slide and wait the saving status. + 6. Delete existing comments on the last slide and wait the saving + status. + 7. Close the browser. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + + Returns: + dict: Dictionary containing variables to be used in + measurements process. + """ + slide_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + login_time = open_and_load_account_document_or_drive( + driver, self.new_doc_url, self.doc_account_type + ) + wait_visibility_of_document_docs_bars(driver, slide_wait) + navigate_to_last_slide(driver, slide_wait) + check_view_mode_if_open_needed(driver) + add_comment(driver, slide_wait, index_of_comment_button=13) + return { + "period_of_login_time": login_time, + "human_delay": HumanDelay.GOOGLE_PPT_WEB, + } + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + def teardown(self): + LOGGER.info(f"start teardown method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the teardown of use case for Google Slides. + + This function will perform the following: + 1. Open the testing Google Drive using the browser. + 2. Wait until the login page finish reloading then login + to the Google account. + 3. delete the copy of original slides document from Google Drive. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + slide_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + delete_created_document_from_google_drive( + driver, slide_wait, self.new_doc_name + ) + + run() + LOGGER.info(f"end of teardown method in {self.__class__.__name__}") + + +if __name__ == "__main__": + PowerPointGoogleWebTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/power_point/ppt_microsoft_web.py b/contrib/cros_ca/automated_use_cases/power_point/ppt_microsoft_web.py new file mode 100644 index 0000000..fa15040 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/power_point/ppt_microsoft_web.py
@@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Microsoft PowerPoint web use case.""" +import uuid + +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_and_load_account_document_or_drive, +) +from automated_use_cases.common_utility.microsoft_documents_utils import ( + switch_to_document_frame, + check_ribbon_container_if_open_needed, + delete_created_document_from_one_drive, + upload_document_to_one_drive, +) +from automated_use_cases.power_point.utils.ppt_microsoft_utils import ( + navigate_to_last_slide, + add_comment_to_last_slide, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from common.common_utility import get_file_path_to_upload +from common.constants import ( + UseCasesURLS, + PageLoadConfig, + DocumentAccountType, + HumanDelay, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() + +BROWSER_PROCESS_NAME = get_browser_process_name() + + +class PowerPointMSWebTest(BenchmarkingTest): + """Test class for Microsoft PowerPoint web use case.""" + + def __init__(self): + self.new_doc_url = None + self.doc_extension_type = ".pptx" + self.new_doc_name = str(uuid.uuid4()) + self.doc_extension_type + self.frame_name = "WacFrame_PowerPoint_0" + self.doc_account_type = DocumentAccountType.MICROSOFT + self.account_drive_url = UseCasesURLS.MS_DRIVE_URL_PATH + self.ppt_file_path = get_file_path_to_upload( + self.new_doc_name, self.doc_extension_type + ) + + def set_up(self): + LOGGER.info(f"start set_up method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the set_up of use case for Microsoft web Slides. + + This function will perform the following: + 1. Open the testing One Drive using the browser. + 2. Wait until the login page finish reloading then login + to the Microsoft account. + 3. Create new PowerPoint document. + 4. Add dummy data to PowerPoint document. + 5. Change PowerPoint document name to be equal uuid. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + ppt_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + self.new_doc_url = upload_document_to_one_drive( + driver, + ppt_wait, + self.new_doc_name, + self.frame_name, + self.ppt_file_path, + ) + + run() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run Microsoft web Slides use case. + + This function will perform the following: + 1. Open the testing Microsoft Slides using the browser. + 2. Wait until the login page finish reloading then login to + the Microsoft account. + 3. Wait until testing Microsoft Slides page finish reloading. + 4. Navigate to the last slide. + 5. Add comment on the last slide and wait the saving status. + 6. Delete existing comments on the last slide and wait the + saving status. + 7. Close the browser. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + + Returns: + dict: Dictionary containing variables to be used in + measurements process. + """ + ppt_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + login_time = open_and_load_account_document_or_drive( + driver, self.new_doc_url, self.doc_account_type + ) + switch_to_document_frame(driver, ppt_wait, self.frame_name) + navigate_to_last_slide(driver, ppt_wait) + check_ribbon_container_if_open_needed(driver) + add_comment_to_last_slide(driver, ppt_wait) + return { + "period_of_login_time": login_time, + "human_delay": HumanDelay.MICROSOFT_PPT_WEB, + } + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + def teardown(self): + LOGGER.info(f"start teardown method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the teardown of use case for Microsoft web Slides. + + This function will perform the following: + 1. Open the testing Microsoft One Drive using the browser. + 2. Wait until the login page finish reloading then login to + the Microsoft account. + 3. delete the copy of original slides document from One Drive. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + ppt_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + delete_created_document_from_one_drive( + driver, ppt_wait, self.new_doc_name + ) + + run() + LOGGER.info(f"end of teardown method in {self.__class__.__name__}") + + +if __name__ == "__main__": + PowerPointMSWebTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/power_point/ppt_native_desktop.py b/contrib/cros_ca/automated_use_cases/power_point/ppt_native_desktop.py new file mode 100644 index 0000000..95b4e6c --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/power_point/ppt_native_desktop.py
@@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Microsoft PowerPoint native desktop use case.""" +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_win_app_driver, + close_win_app_driver, +) +from automated_use_cases.common_utility.microsoft_documents_utils import ( + close_native_app_document_without_save, + switch_to_native_app_window, +) +from automated_use_cases.power_point.utils.ppt_native_desktop_utils import ( + navigate_to_last_slide, + add_comment_to_last_slide, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup +from common.constants import ProcessNames, PageLoadConfig, LoggingConfig + +LOGGER = LoggingConfig.get_logger() + +PROCESS_NAME = (ProcessNames.PPT, ProcessNames.AI_PPT) + + +class PowerPointNativeDesktopTest(BenchmarkingTest): + """Test class for Microsoft PowerPoint native desktop use case.""" + + def set_up(self): + LOGGER.info(f"starting set_up method in {self.__class__.__name__}.") + open_win_app_driver() + LOGGER.info(f"ending of set_up method in {self.__class__.__name__}.") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}.") + + @performance(process_name=PROCESS_NAME) + @setup(process_name=PROCESS_NAME, is_execution_part=True) + def run(driver): + """Run Microsoft native desktop Slides use case. + + This function will perform the following: + 1. Open the testing Microsoft Slides using the native + PowerPoint app. + 2. Wait until testing Microsoft PowerPoint be visible. + 4. Navigate to the last slide. + 5. Add comment on the last slide. + 6. Close the app and select don't save option. + + This function is decorated with the @setup decorator, + configuring the driver for the native desktop app processes. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + """ + wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + switch_to_native_app_window(driver, wait) + navigate_to_last_slide(driver) + add_comment_to_last_slide(driver, wait) + close_native_app_document_without_save(driver) + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}.") + + def teardown(self): + LOGGER.info(f"starting teardown method in {self.__class__.__name__}.") + close_win_app_driver() + LOGGER.info(f"ending of teardown method in {self.__class__.__name__}.") + + +if __name__ == "__main__": + PowerPointNativeDesktopTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/power_point/utils/__init__.py b/contrib/cros_ca/automated_use_cases/power_point/utils/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/power_point/utils/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/power_point/utils/ppt_google_utils.py b/contrib/cros_ca/automated_use_cases/power_point/utils/ppt_google_utils.py new file mode 100644 index 0000000..4747930 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/power_point/utils/ppt_google_utils.py
@@ -0,0 +1,38 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Google Slides Web use case.""" +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC + +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +@time_measure() +def navigate_to_last_slide(driver, wait): + """Function to navigate to last slide. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start navigate_to_last_slide method.") + wait.until( + EC.visibility_of_element_located( + (By.CLASS_NAME, "punch-filmstrip-thumbnail-background") + ) + ) + first_slide = driver.find_elements_by_class_name( + "punch-filmstrip-thumbnail-background" + )[0] + actions = ActionChains(driver) + actions.move_to_element(first_slide).click(first_slide).send_keys( + Keys.END + ).perform() + LOGGER.info("end of navigate_to_last_slide method.")
diff --git a/contrib/cros_ca/automated_use_cases/power_point/utils/ppt_microsoft_utils.py b/contrib/cros_ca/automated_use_cases/power_point/utils/ppt_microsoft_utils.py new file mode 100644 index 0000000..5c3fe9c --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/power_point/utils/ppt_microsoft_utils.py
@@ -0,0 +1,104 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Microsoft PowerPoint web use case utils.""" +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC + +from automated_use_cases.common_utility.microsoft_documents_utils import ( + wait_saving_status, +) +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +@time_measure() +def navigate_to_last_slide(driver, wait): + """function to navigate to last slide. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start navigate_to_last_slide method.") + wait.until(EC.visibility_of_element_located((By.ID, "WACViewPanel"))) + first_slide_path = ( + "//div[contains(@id,'grid-content-view-') and " + "starts-with(@aria-posinset,'1')]" + ) + wait.until(EC.visibility_of_element_located((By.XPATH, first_slide_path))) + first_slide = driver.find_element_by_xpath(first_slide_path) + actions = ActionChains(driver) + actions.move_to_element(first_slide).click(first_slide).send_keys( + Keys.END + ).perform() + LOGGER.info("ending of navigate_to_last_slide method.") + + +@time_measure() +def add_comment_to_last_slide(driver, wait): + """function to add comments in document. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start add_comment_to_last_slide method.") + wait.until(EC.visibility_of_element_located((By.ID, "Insert"))) + driver.find_element_by_id("Insert").click() + insert_button_disabled_xpath = ( + "//button[contains(@id," + "'InsertComment') and contains" + "(@aria-disabled, 'true')]" + ) + wait.until_not( + EC.visibility_of_element_located( + (By.XPATH, insert_button_disabled_xpath) + ) + ) + wait.until(EC.element_to_be_clickable((By.ID, "InsertComment"))) + driver.find_element_by_id("InsertComment").click() + wait.until( + EC.visibility_of_element_located( + (By.ID, "EditingCommentsPanelFocus-panel") + ) + ) + wait.until(EC.visibility_of_element_located((By.ID, "NewCommentButton"))) + driver.find_element_by_id("NewCommentButton").click() + wait.until( + EC.visibility_of_element_located((By.ID, "CommentsListScrollView")) + ) + comment_input_section_xpath = ( + "//div[contains(@class,'ql-editor " + "ql-blank') and (contains" + "(@aria-label, '@mention or " + "comment') or contains(@aria-label," + " 'Start a conversation'))]" + ) + wait.until( + EC.visibility_of_element_located( + (By.XPATH, comment_input_section_xpath) + ) + ) + comment_input_section = driver.find_element_by_xpath( + comment_input_section_xpath + ) + comment_input_section.click() + comment_input_section.send_keys("Hi.") + send_replay_button_xpath = ( + "//button[starts-with(@id,'sendReplyButton') " + "and contains(@aria-label, " + "'Post comment (Ctrl + Enter)')]" + ) + wait.until( + EC.visibility_of_element_located((By.XPATH, send_replay_button_xpath)) + ) + driver.find_element_by_xpath(send_replay_button_xpath).click() + wait_saving_status(wait) + LOGGER.info("end of add_comment_to_last_slide method.")
diff --git a/contrib/cros_ca/automated_use_cases/power_point/utils/ppt_native_desktop_utils.py b/contrib/cros_ca/automated_use_cases/power_point/utils/ppt_native_desktop_utils.py new file mode 100644 index 0000000..cba1e78 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/power_point/utils/ppt_native_desktop_utils.py
@@ -0,0 +1,52 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Microsoft PowerPoint native desktop use case utils.""" + +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC + +from automated_use_cases.common_utility.microsoft_documents_utils import ( + write_comment_on_native_app_document, + insert_comment_on_native_app_document, +) +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +@time_measure() +def navigate_to_last_slide(driver): + """function to navigate to last slide. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + """ + LOGGER.info("start navigate_to_last_slide method.") + actions = ActionChains(driver) + actions.send_keys(Keys.END).perform() + LOGGER.info("end of navigate_to_last_slide method.") + + +@time_measure() +def add_comment_to_last_slide(driver, wait): + """function to add comment. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start add_comment_to_last_slide method.") + insert_comment_on_native_app_document(driver, wait) + wait.until(EC.visibility_of_element_located((By.NAME, "Comments Pane"))) + wait.until( + EC.visibility_of_element_located((By.NAME, "New comment")) + ).click() + write_comment_on_native_app_document(driver, wait) + LOGGER.info("end add_comment_to_last_slide method.")
diff --git a/contrib/cros_ca/automated_use_cases/video_record/__init__.py b/contrib/cros_ca/automated_use_cases/video_record/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/video_record/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/video_record/windows_video.py b/contrib/cros_ca/automated_use_cases/video_record/windows_video.py new file mode 100644 index 0000000..ea8fdae --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/video_record/windows_video.py
@@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Windows Photos Editing/Crop native desktop use case.""" +import os + +import pyautogui +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.get_website_util import ( + load_website_by_url, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import ( + setup, + get_windows_chrome_web_driver, +) +from benchmarking.time_benchmark import time_measure +from common.constants import ( + ProcessNames, + PageLoadConfig, + DriversAndAppsPaths, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() + +PROCESS_NAME = ( + ProcessNames.WIN_GAME_BAR_APP, + ProcessNames.WIN_GAME_BAR_SERVER_APP, +) + + +@time_measure() +def record_video(recorded_video_time): + """Function to start recording a video for specific period of time + using Game Bar application for windows. + + Args: + recorded_video_time: Specific period of time indicates + total video time + """ + LOGGER.info( + "start record_video method with " + f"recorded_video_time = {recorded_video_time}." + ) + # start video recording + pyautogui.hotkey("win", "alt", "r") + # sleep for total video time + pyautogui.sleep(recorded_video_time) + # stop video recording + pyautogui.hotkey("win", "alt", "r") + LOGGER.info("end of record_video method.") + + +class WindowsVideoRecordingNativeDesktopTest(BenchmarkingTest): + """Test class for Windows Photos Editing/Crop native desktop use case.""" + + def __init__(self): + self.driver = None + self.video_url = "https://www.youtube.com/watch?v=YXMo5w9aMNs" + self.video_name_prefix = "Wolves 101 _ Nat Geo Wild" + self.recorded_video_time = 10 + self.videos_dir = DriversAndAppsPaths.WINDOWS_VIDEOS_CAPTURES_DIR_PATH + + def set_up(self): + """This method will perform the following: + 1. Get Windows Chrome Web Driver + 2. Open and load specific video url like YouTube in this case. + 3. Wait cookies popup and reject it. + """ + LOGGER.info(f"start set_up method in {self.__class__.__name__}") + self.driver, _ = get_windows_chrome_web_driver() + self.driver.maximize_window() + wait = WebDriverWait(self.driver, PageLoadConfig.WAIT_TIMEOUT) + load_website_by_url(self.driver, self.video_url) + wait.until( + EC.element_to_be_clickable( + (By.XPATH, "//button[starts-with(@aria-label,'Reject')]") + ) + ).click() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=PROCESS_NAME) + @setup(process_name=PROCESS_NAME, is_execution_part=True) + def run(): + """Run Windows Video Recording native desktop use case + using Game Bar application for windows. + + This function will perform the following: + + 1. Start recording the YouTube tab in Chrome browser for + specific time like 10s in this case. + 2. Stop recording the video. + """ + record_video(self.recorded_video_time) + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + def teardown(self): + """This method will perform delete for recorded video from + Videos/Captures directory path in Windows platform + and quit opened webdriver.""" + LOGGER.info(f"start teardown method in {self.__class__.__name__}") + self.driver.quit() + files_name = os.listdir(self.videos_dir) + for file_name in files_name: + if file_name.startswith(self.video_name_prefix): + os.remove(self.videos_dir + rf"\{file_name}") + LOGGER.info(f"end of teardown method in {self.__class__.__name__}") + + +if __name__ == "__main__": + WindowsVideoRecordingNativeDesktopTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/word/__init__.py b/contrib/cros_ca/automated_use_cases/word/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/word/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/word/utils/__init__.py b/contrib/cros_ca/automated_use_cases/word/utils/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/word/utils/__init__.py
diff --git a/contrib/cros_ca/automated_use_cases/word/utils/word_google_utils.py b/contrib/cros_ca/automated_use_cases/word/utils/word_google_utils.py new file mode 100644 index 0000000..139b738 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/word/utils/word_google_utils.py
@@ -0,0 +1,61 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Google Doc web use case utils.""" +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC + +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +def pick_first_page_canvas(driver, wait): + """Function to select first page canvas. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + + Returns: + first page canvas + """ + LOGGER.info("start pick_first_page_canvas method.") + wait.until(EC.visibility_of_element_located((By.ID, "kix-appview"))) + first_page_path = ( + "//div[contains(@class,'kix-page-paginated canvas-first-page')]" + ) + wait.until(EC.visibility_of_element_located((By.XPATH, first_page_path))) + first_page_canvas = driver.find_element_by_xpath( + first_page_path + ).find_element_by_class_name("kix-canvas-tile-content") + LOGGER.info("end of pick_first_page_canvas method.") + return first_page_canvas + + +@time_measure() +def navigate_to_last_page(driver, wait): + """Function to navigate to last page. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start navigate_to_last_page method.") + first_page_canvas = pick_first_page_canvas(driver, wait) + actions = ActionChains(driver) + ( + actions.move_to_element(first_page_canvas) + .click(first_page_canvas) + .key_down(Keys.CONTROL) + .send_keys(Keys.END) + .key_up(Keys.CONTROL) + .perform() + ) + LOGGER.info("end of navigate_to_last_page method.")
diff --git a/contrib/cros_ca/automated_use_cases/word/utils/word_microsoft_utils.py b/contrib/cros_ca/automated_use_cases/word/utils/word_microsoft_utils.py new file mode 100644 index 0000000..add4f05 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/word/utils/word_microsoft_utils.py
@@ -0,0 +1,93 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Microsoft Word web use case utils.""" +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC + +from automated_use_cases.common_utility.microsoft_documents_utils import ( + wait_saving_status, +) +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +@time_measure() +def navigate_to_last_page(driver, wait): + """Function to navigate to last page. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start navigate_to_last_page method.") + wait.until( + EC.visibility_of_element_located( + (By.ID, "PaginatedContentContainerDisplayWrapper") + ) + ) + pages_container = driver.find_element_by_id( + "PaginatedContentContainerDisplayWrapper" + ) + normal_text = pages_container.find_elements_by_class_name("NormalTextRun")[ + 0 + ] + normal_text.click() + normal_text.send_keys(Keys.CONTROL, Keys.END) + LOGGER.info("end of navigate_to_last_page method.") + + +@time_measure() +def add_comment_to_last_page(driver, wait): + """Function to add comments in document. + + Args: + driver: The WebDriver instance used to interact with the browser. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start add_comment_to_last_page method.") + wait.until(EC.visibility_of_element_located((By.ID, "Insert"))) + driver.find_element_by_id("Insert").click() + insert_button_disabled_xpath = ( + "//button[contains(@id," + "'NewComment') and contains" + "(@aria-disabled, 'true')]" + ) + wait.until_not( + EC.visibility_of_element_located( + (By.XPATH, insert_button_disabled_xpath) + ) + ) + wait.until(EC.element_to_be_clickable((By.ID, "NewComment"))) + driver.find_element_by_id("NewComment").click() + + last_card_editor_xpath = ( + "//div[starts-with(@id,'cardEditor_') " + "and contains(@class, " + "'ql-editor ql-blank') and " + "(contains(@data-placeholder, " + "'@mention or comment') or contains" + "(@data-placeholder, " + "'Start a conversation'))]" + ) + wait.until( + EC.visibility_of_element_located((By.XPATH, last_card_editor_xpath)) + ) + last_card_editor = driver.find_element_by_xpath(last_card_editor_xpath) + last_card_editor.click() + last_card_editor.send_keys("Hi.") + send_button_xpath = ( + "//button[starts-with(@id,'sendReplyButton')" + " and contains(@aria-label, 'Post comment " + "(Ctrl + Enter)')]" + ) + wait.until(EC.visibility_of_element_located((By.XPATH, send_button_xpath))) + driver.find_element_by_xpath(send_button_xpath).click() + wait_saving_status(wait) + LOGGER.info("end of add_comment_to_last_page method.")
diff --git a/contrib/cros_ca/automated_use_cases/word/utils/word_native_desktop_utils.py b/contrib/cros_ca/automated_use_cases/word/utils/word_native_desktop_utils.py new file mode 100644 index 0000000..8c1157a --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/word/utils/word_native_desktop_utils.py
@@ -0,0 +1,55 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Microsoft Word native desktop app use case utils.""" + +from selenium.webdriver import ActionChains +from selenium.webdriver.common.keys import Keys + +from automated_use_cases.common_utility.microsoft_documents_utils import ( + write_comment_on_native_app_document, + insert_comment_on_native_app_document, +) +from benchmarking.time_benchmark import time_measure +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +@time_measure() +def navigate_to_last_page(driver): + """Function to navigate to last page. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + """ + LOGGER.info("start navigate_to_last_page method.") + doc_body = driver.find_elements_by_accessibility_id("Body") + first_doc_body = doc_body[0] + actions = ActionChains(driver) + ( + actions.move_to_element(first_doc_body) + .click(first_doc_body) + .key_down(Keys.CONTROL) + .send_keys(Keys.END) + .key_up(Keys.CONTROL) + .perform() + ) + LOGGER.info("ending of navigate_to_last_page method.") + + +@time_measure() +def add_comment_to_last_page(driver, wait): + """Function to add comment to last page. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + wait: Explicit wait used while checking the expected conditions + of the elements. + """ + LOGGER.info("start add_comment_to_last_page method.") + insert_comment_on_native_app_document(driver, wait) + write_comment_on_native_app_document(driver, wait) + LOGGER.info("end of add_comment_to_last_page method.")
diff --git a/contrib/cros_ca/automated_use_cases/word/word_google_web.py b/contrib/cros_ca/automated_use_cases/word/word_google_web.py new file mode 100644 index 0000000..7870835 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/word/word_google_web.py
@@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Google Doc web use case.""" +import uuid + +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_and_load_account_document_or_drive, +) +from automated_use_cases.common_utility.google_documents_utils import ( + wait_visibility_of_document_docs_bars, + check_view_mode_if_open_needed, + add_comment, + delete_created_document_from_google_drive, + upload_document_to_google_drive, +) +from automated_use_cases.word.utils.word_google_utils import ( + navigate_to_last_page, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from common.common_utility import get_file_path_to_upload +from common.constants import ( + UseCasesURLS, + PageLoadConfig, + DocumentAccountType, + HumanDelay, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() + +BROWSER_PROCESS_NAME = get_browser_process_name() + + +class WordGoogleWebTest(BenchmarkingTest): + """Test class for Google Doc web use case.""" + + def __init__(self): + self.new_doc_url = None + self.doc_extension_type = ".docx" + self.new_doc_name = str(uuid.uuid4()) + self.doc_extension_type + self.doc_account_type = DocumentAccountType.GOOGLE + self.account_drive_url = UseCasesURLS.GOOGLE_DRIVE_URL_PATH + self.word_file_path = get_file_path_to_upload( + self.new_doc_name, self.doc_extension_type + ) + + def set_up(self): + LOGGER.info(f"start set_up method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the set_up of use case for Google Doc. + + This function will perform the following: + 1. Open the testing Google Drive using the browser. + 2. Wait until the login page finish reloading then login + to the Google account. + 3. Create new doc document. + 4. Add dummy data to doc document. + 5. Change doc document name to be equal uuid. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + doc_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + self.new_doc_url = upload_document_to_google_drive( + driver, doc_wait, self.word_file_path, self.new_doc_name + ) + + run() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run Google Doc use case. + + This function will perform the following: + 1. Open the testing Google Doc using the browser. + 2. Wait until the login page finish reloading then login + to the Google account. + 3. Wait until testing Google Doc page finish reloading. + 3. Navigate to the last page. + 4. Add comment on the last page and wait the saving status. + 5. Delete existing comments on the last page and wait + the saving status. + 7. Close the browser. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + + Returns: + dict: Dictionary containing variables to be used in + measurements process. + """ + doc_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + login_time = open_and_load_account_document_or_drive( + driver, self.new_doc_url, self.doc_account_type + ) + wait_visibility_of_document_docs_bars(driver, doc_wait) + navigate_to_last_page(driver, doc_wait) + check_view_mode_if_open_needed(driver) + add_comment(driver, doc_wait, index_of_comment_button=17) + return { + "period_of_login_time": login_time, + "human_delay": HumanDelay.GOOGLE_WORD_WEB, + } + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + def teardown(self): + LOGGER.info(f"start teardown method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the teardown of use case for Google Docs. + + This function will perform the following: + 1. Open the testing Google Drive using the browser. + 2. Wait until the login page finish reloading then login + to the Google account. + 3. delete the copy of original doc document from Google Drive. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + doc_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + delete_created_document_from_google_drive( + driver, doc_wait, self.new_doc_name + ) + + run() + LOGGER.info(f"end of teardown method in {self.__class__.__name__}") + + +if __name__ == "__main__": + WordGoogleWebTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/word/word_microsoft_web.py b/contrib/cros_ca/automated_use_cases/word/word_microsoft_web.py new file mode 100644 index 0000000..66dfc87 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/word/word_microsoft_web.py
@@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Microsoft Word web use case.""" +import uuid + +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_and_load_account_document_or_drive, +) +from automated_use_cases.common_utility.microsoft_documents_utils import ( + switch_to_document_frame, + check_ribbon_container_if_open_needed, + delete_created_document_from_one_drive, + upload_document_to_one_drive, +) +from automated_use_cases.word.utils.word_microsoft_utils import ( + navigate_to_last_page, + add_comment_to_last_page, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup, get_browser_process_name +from common.common_utility import get_file_path_to_upload +from common.constants import ( + UseCasesURLS, + PageLoadConfig, + DocumentAccountType, + HumanDelay, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() + +BROWSER_PROCESS_NAME = get_browser_process_name() + + +class WordMicrosoftWebTest(BenchmarkingTest): + """Test class for Microsoft Word web use case.""" + + def __init__(self): + self.new_doc_url = None + self.doc_extension_type = ".docx" + self.new_doc_name = str(uuid.uuid4()) + self.doc_extension_type + self.frame_name = "WacFrame_Word_0" + self.doc_account_type = DocumentAccountType.MICROSOFT + self.account_drive_url = UseCasesURLS.MS_DRIVE_URL_PATH + self.word_file_path = get_file_path_to_upload( + self.new_doc_name, self.doc_extension_type + ) + + def set_up(self): + LOGGER.info(f"start set_up method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the set_up of use case for Microsoft web Word. + + This function will perform the following: + 1. Open the testing One Drive using the browser. + 2. Wait until the login page finish reloading then login + to the Microsoft account. + 3. Create new Word document. + 4. Add dummy data to Word document. + 5. Change Word document name to be equal uuid. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + word_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + self.new_doc_url = upload_document_to_one_drive( + driver, + word_wait, + self.new_doc_name, + self.frame_name, + self.word_file_path, + ) + + run() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}") + + @performance(process_name=BROWSER_PROCESS_NAME) + @setup( + is_browsing=True, + process_name=BROWSER_PROCESS_NAME, + is_execution_part=True, + ) + def run(driver): + """Run Microsoft web Word use case. + + This function will perform the following: + 1. Open the testing Microsoft Word using the browser. + 2. Wait until the login page finish reloading then login to + the Microsoft account. + 3. Wait until testing Microsoft Word page finish reloading. + 4. Navigate to the last page. + 5. Add comment on the last page and wait the saving status. + 6. Delete existing comments on the last page and wait + the saving status. + 7. Close the browser. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + + Returns: + dict: Dictionary containing variables to be used in + measurements process. + """ + word_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + login_time = open_and_load_account_document_or_drive( + driver, self.new_doc_url, self.doc_account_type + ) + switch_to_document_frame(driver, word_wait, self.frame_name) + navigate_to_last_page(driver, word_wait) + check_ribbon_container_if_open_needed(driver) + add_comment_to_last_page(driver, word_wait) + return { + "period_of_login_time": login_time, + "human_delay": HumanDelay.MICROSOFT_WORD_WEB, + } + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}") + + def teardown(self): + LOGGER.info(f"start teardown method in {self.__class__.__name__}") + + @setup(is_browsing=True, process_name=BROWSER_PROCESS_NAME) + def run(driver): + """Run the teardown of use case for Microsoft web Word. + + This function will perform the following: + 1. Open the testing Microsoft One Drive using the browser. + 2. Wait until the login page finish reloading then login to + the Microsoft account. + 3. delete the copy of original word document from One Drive. + + This function is decorated with the @setup decorator, + configuring the driver for the browser processes. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + word_wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + open_and_load_account_document_or_drive( + driver, self.account_drive_url, self.doc_account_type + ) + delete_created_document_from_one_drive( + driver, word_wait, self.new_doc_name + ) + + run() + LOGGER.info(f"end of teardown method in {self.__class__.__name__}") + + +if __name__ == "__main__": + WordMicrosoftWebTest().run_test()
diff --git a/contrib/cros_ca/automated_use_cases/word/word_native_desktop.py b/contrib/cros_ca/automated_use_cases/word/word_native_desktop.py new file mode 100644 index 0000000..1781eb7 --- /dev/null +++ b/contrib/cros_ca/automated_use_cases/word/word_native_desktop.py
@@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= E1120, W1203 +"""Microsoft Word native desktop app use case.""" +from selenium.webdriver.support.wait import WebDriverWait + +from automated_use_cases.common_utility.doc_editing_utils import ( + open_win_app_driver, + close_win_app_driver, +) +from automated_use_cases.common_utility.microsoft_documents_utils import ( + close_native_app_document_without_save, + switch_to_native_app_window, +) +from automated_use_cases.word.utils.word_native_desktop_utils import ( + navigate_to_last_page, + add_comment_to_last_page, +) +from benchmarking.benchmarking_test import ( + BenchmarkingTest, +) +from benchmarking.performance_decorator import performance +from benchmarking.platform_benchmarking import setup +from common.constants import ProcessNames, PageLoadConfig, LoggingConfig + +LOGGER = LoggingConfig.get_logger() + +PROCESS_NAME = (ProcessNames.WORD,) + + +class WordNativeDesktopTest(BenchmarkingTest): + """Test class for Microsoft Word native desktop app use case.""" + + def set_up(self): + LOGGER.info(f"start set_up method in {self.__class__.__name__}.") + open_win_app_driver() + LOGGER.info(f"end of set_up method in {self.__class__.__name__}.") + + def execute(self): + LOGGER.info(f"start execute method in {self.__class__.__name__}.") + + @performance(process_name=PROCESS_NAME) + @setup(process_name=PROCESS_NAME, is_execution_part=True) + def run(driver): + """Run Microsoft native desktop Word use case. + + This function will perform the following: + 1. Open the testing Microsoft Word using the native Word app. + 2. Wait until testing Microsoft Word be visible. + 4. Navigate to the last page. + 5. Add comment on the last page + 6. Close the app and select don't save option. + + This function is decorated with the @setup decorator, + configuring the driver for the native desktop app processes. + + Args: + driver: The Windows Desktop App Driver instance used to + interact with the desktop app. + """ + wait = WebDriverWait(driver, PageLoadConfig.WAIT_TIMEOUT) + switch_to_native_app_window(driver, wait) + navigate_to_last_page(driver) + add_comment_to_last_page(driver, wait) + close_native_app_document_without_save(driver) + + run() + LOGGER.info(f"end of execute method in {self.__class__.__name__}.") + + def teardown(self): + LOGGER.info(f"start teardown method in {self.__class__.__name__}.") + close_win_app_driver() + LOGGER.info(f"end of teardown method in {self.__class__.__name__}.") + + +if __name__ == "__main__": + WordNativeDesktopTest().run_test()
diff --git a/contrib/cros_ca/benchmarking/__init__.py b/contrib/cros_ca/benchmarking/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/benchmarking/__init__.py
diff --git a/contrib/cros_ca/benchmarking/benchmark.py b/contrib/cros_ca/benchmarking/benchmark.py new file mode 100644 index 0000000..5cc4b2e --- /dev/null +++ b/contrib/cros_ca/benchmarking/benchmark.py
@@ -0,0 +1,104 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Script contains Benchmark class.""" + +from benchmarking.process_measurements import ProcessMeasurements +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() +RESULTS_LOGGER = LoggingConfig.get_logger(False) + + +class Benchmark: + """Benchmark class used to benchmark set of process + used while run the use cases. + """ + + def __init__(self): + """Constructor of Benchmark class to have + empty set of ProcessMeasurements type at initialization. + """ + self.__process_measurements_set = set() + + def get_process_measurements_set(self): + """ + Getter method for __process_measurements_set. + Returns: + __process_measurements_set + """ + return self.__process_measurements_set + + def update_process_measurements_set(self, proces_measure): + """Method to update __process_measurements_set by add + the ProcessMeasurements to the set. + + Args: + proces_measure: ProcessMeasurements to add to the set. + """ + self.get_process_measurements_set().add(proces_measure) + + def measure(self, proces_measurements: ProcessMeasurements): + """Method to run measurements operation on ProcessMeasurements set. + if ProcessMeasurements passed by parameter not exist in the set before, + then it will take the measurements for it and added it to the set, + else it will take the measurements for current process in the set. + + Args: + proces_measurements: ProcessMeasurements to run the measure for it. + """ + for current_proces_measure in self.get_process_measurements_set(): + if current_proces_measure == proces_measurements: + current_proces_measure.appends_process_measurements() + break + else: + proces_measurements.appends_process_measurements() + self.update_process_measurements_set(proces_measurements) + + def print_average(self): + """Method to print result of measurements for + the __process_measurements_set. + """ + LOGGER.info(f"start print_average method in {self.__class__.__name__}.") + for proces_measurement in self.get_process_measurements_set(): + proces_measurement.print_average_with_outlier() + + app_cpu_percent_sum = 0.0 + app_memory_percent_sum = 0.0 + app_user_cpu_times_sum = 0.0 + app_system_cpu_times_sum = 0.0 + + for proces_measurement in self.get_process_measurements_set(): + average_dict = proces_measurement.average_dict + cpu_percent = average_dict.get("cpu_percent") + memory_percent = average_dict.get("memory_percent") + user_cpu_times = average_dict.get("user_cpu_times") + system_cpu_times = average_dict.get("system_cpu_times") + app_cpu_percent_sum += cpu_percent if cpu_percent > 0 else 0 + app_memory_percent_sum += ( + memory_percent if memory_percent > 0 else 0 + ) + app_user_cpu_times_sum += ( + user_cpu_times if user_cpu_times > 0 else 0 + ) + app_system_cpu_times_sum += ( + system_cpu_times if system_cpu_times > 0 else 0 + ) + RESULTS_LOGGER.info(f"{15*'-'}sum per all process{15*'-'}") + RESULTS_LOGGER.info( + f"sum of app cpu percent = ({app_cpu_percent_sum}) %" + ) + RESULTS_LOGGER.info( + f"sum of app memory percent = ({app_memory_percent_sum}) %" + ) + RESULTS_LOGGER.info( + f"sum of app user cpu time = ({app_user_cpu_times_sum}) seconds" + ) + RESULTS_LOGGER.info( + f"sum of app system cpu time = ({app_system_cpu_times_sum}) " + "seconds" + ) + LOGGER.info( + f"end of print_average method in {self.__class__.__name__}." + )
diff --git a/contrib/cros_ca/benchmarking/benchmarking_exception.py b/contrib/cros_ca/benchmarking/benchmarking_exception.py new file mode 100644 index 0000000..6fc7b2c --- /dev/null +++ b/contrib/cros_ca/benchmarking/benchmarking_exception.py
@@ -0,0 +1,10 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Script contains Benchmarking Exception Class.""" + + +class BenchmarkingException(Exception): + """A custom exception that will be raised when there is exception in + benchmarking operation. + """
diff --git a/contrib/cros_ca/benchmarking/benchmarking_test.py b/contrib/cros_ca/benchmarking/benchmarking_test.py new file mode 100644 index 0000000..d47d089 --- /dev/null +++ b/contrib/cros_ca/benchmarking/benchmarking_test.py
@@ -0,0 +1,43 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W0718 +"""Script contains BenchmarkingTest class used to be +inherited by project use cases.""" +import traceback + +from common.constants import LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +class BenchmarkingTest: + """Class used to be inherited by project use cases.""" + + def set_up(self): + """Method will be run before executing main logic of use case, + to prepare required use case data.""" + + def execute(self): + """Method responsible for executing core logic of the use case.""" + + def teardown(self): + """Method will be run after executing main logic of use case, + to delete use case data.""" + + def run_test(self): + """Method to run the test based on special order""" + LOGGER.info("start running test") + try: + self.set_up() + self.execute() + except Exception as e: + LOGGER.exception(e) + traceback.print_exception(type(e), e, e.__traceback__) + finally: + try: + self.teardown() + except Exception as e: + LOGGER.exception(e) + traceback.print_exception(type(e), e, e.__traceback__) + LOGGER.info("end of running test")
diff --git a/contrib/cros_ca/benchmarking/performance_decorator.py b/contrib/cros_ca/benchmarking/performance_decorator.py new file mode 100644 index 0000000..19135d6 --- /dev/null +++ b/contrib/cros_ca/benchmarking/performance_decorator.py
@@ -0,0 +1,129 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W1203 +"""Script contains performance decorator used to running +benchmarking thread. +""" + +import platform +import threading + +import psutil +from psutil import NoSuchProcess + +from benchmarking.benchmark import Benchmark +from benchmarking.benchmarking_exception import BenchmarkingException +from benchmarking.process_measurements import ProcessMeasurements +from common.constants import ProcessNames, LoggingConfig + +LOGGER = LoggingConfig.get_logger() + + +def measurements_thread(process_name, stop_event, benchmarking): + """Helper function that's responsible for measuring the device + resource usage in parallel with the current process. + + Args: + benchmarking: Instance of Benchmark class used to benchmark + set of process used while run the use cases. + process_name: Tuple of process name needs to take measurements for it. + stop_event: A flag to stop the measurement. + """ + + def performance_measurements(): + """Thread function responsible to take measurements every 100ms + for pre-defined process name. + """ + try: + for proc in psutil.process_iter(): + for proces_name in process_name: + p_name = proc.name() + if ( + proces_name in p_name + and p_name + not in ProcessNames.PROCESS_NAME_TO_BE_EXCLUDE + ): + benchmarking.measure(ProcessMeasurements(proc)) + except NoSuchProcess as exc: + stop_event.set() # Stop the measurement if the process is not found + LOGGER.info( + f"stop performance_measurements thread, " + f"because proces not found with exception : {exc}" + ) + + if not stop_event.is_set(): + threading.Timer( + interval=0.1, function=performance_measurements + ).start() + + # Start the initial measurement + LOGGER.info("start performance_measurements thread.") + performance_measurements() + + +def validate_process_name(process_name): + """Function to validate process name passed by running platform. + + Args: + process_name: Tuple of process name needs to take measurements for it. + + Raises: + BenchmarkingException: In case process name passed is None + or empty or if proces name not contains .exe for Windows + platform or proces name contains .exe for linux platform. + """ + LOGGER.info("start validate_process_name method.") + if process_name is None or len(process_name) == 0: + LOGGER.info("validate_process_name failed with BenchmarkingException.") + raise BenchmarkingException("process_names should not be empty.") + windows_app_extension = ".exe" + for proc in process_name: + if platform.system() == ProcessNames.WINDOWS_PLATFORM_TYPE: + if windows_app_extension not in proc.lower(): + LOGGER.info( + "validate_process_name failed with BenchmarkingException." + ) + raise BenchmarkingException( + "Windows proces name should ends with .exe" + ) + else: + if windows_app_extension in proc.lower(): + LOGGER.info( + "validate_process_name failed with BenchmarkingException." + ) + raise BenchmarkingException( + "Linux proces name should not ends with .exe" + ) + LOGGER.info("end of validate_process_name method.") + + +def performance(process_name: tuple = None): + """Decorator function that's responsible for measuring the device + resource usage in parallel with the current process.""" + + def decorator(func): + def wrapper(*args, **kwargs): + LOGGER.info( + "start executing performance decorator with " + f"process_name = {process_name}." + ) + validate_process_name(process_name) + benchmarking = Benchmark() + stop_event = threading.Event() + measurement_thread = threading.Thread( + target=measurements_thread, + args=(process_name, stop_event, benchmarking), + ) + measurement_thread.start() + # Run the function + result = func(*args, **kwargs) + stop_event.set() + measurement_thread.join() + benchmarking.print_average() + LOGGER.info("ending of execute performance decorator.") + return result + + return wrapper + + return decorator
diff --git a/contrib/cros_ca/benchmarking/platform_benchmarking.py b/contrib/cros_ca/benchmarking/platform_benchmarking.py new file mode 100644 index 0000000..76c73f7 --- /dev/null +++ b/contrib/cros_ca/benchmarking/platform_benchmarking.py
@@ -0,0 +1,355 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable= W0603, W1203, C0415 +""" +Script used to determine the platform, and it's requirement +needed before running any use case. +""" + +import platform +import sys +import time +import uuid + +from selenium import webdriver as selenium_chrome_webdriver +from selenium.common.exceptions import NoSuchWindowException + +from benchmarking import time_benchmark +from benchmarking.benchmarking_exception import BenchmarkingException +from common.constants import ( + DriversAndAppsPaths, + UseCasesURLS, + ProcessNames, + UserAgent, + LoggingConfig, +) + +LOGGER = LoggingConfig.get_logger() +RESULTS_LOGGER = LoggingConfig.get_logger(False) + + +def get_browser_process_name(): + """Function used to determined process_name for any use case that + use web task, and to decide which browser will be used based on + first arguments variable passed by command line while running + any use case. + + Returns: + process_name based on OS type and first arguments variable, + so Chrome browser will be the default unless "edge" keyword passed + as first arguments variable to use Edge browser. + """ + LOGGER.info("executing get_browser_process_name method.") + arguments_variable = sys.argv + browser_type = ( + arguments_variable[1] if len(arguments_variable) > 1 else None + ) + if browser_type is None or browser_type == ProcessNames.CHROME_BROWSER_TYPE: + if platform.system() == ProcessNames.WINDOWS_PLATFORM_TYPE: + return (ProcessNames.WINDOWS_CHROME,) + return (ProcessNames.LINUX_CHROME,) + if browser_type == ProcessNames.EDGE_BROWSER_TYPE: + if platform.system() == ProcessNames.WINDOWS_PLATFORM_TYPE: + return (ProcessNames.WINDOWS_EDGE,) + raise BenchmarkingException( + "edge browser for linux platform not supported." + ) + raise BenchmarkingException( + "first arguments passed for web browser is not supported." + ) + + +def get_browser_web_driver(process_name): + """Function used to determined which web driver should be used. + + Args: + process_name: Process_name of use case. + + Returns: + Web driver. + """ + LOGGER.info("executing get_browser_web_driver method.") + if ProcessNames.WINDOWS_CHROME in process_name: + return get_windows_chrome_web_driver() + if ProcessNames.LINUX_CHROME in process_name: + return get_linux_chrome_web_driver() + if ProcessNames.WINDOWS_EDGE in process_name: + return get_windows_edge_web_driver() + raise BenchmarkingException( + "for browsing test, either (Windows, Linux) chrome " + "or (Windows) edge should selected." + ) + + +def get_windows_chrome_web_driver(): + """Function to get Chrome web driver for Windows platform. + + Returns: + Windows Chrome web driver. + """ + LOGGER.info("executing get_windows_chrome_web_driver method.") + chrome_options = selenium_chrome_webdriver.ChromeOptions() + chrome_options.add_argument("--incognito") + chrome_options.add_argument("--disable-application-cache") + chrome_options.add_argument("--disable-extensions") + chrome_options.add_argument(f"--user-agent={get_random_user_agent()}") + from webdriver_manager.chrome import ChromeDriverManager + + chrome_driver_path = ChromeDriverManager().install() + time_before_run_use_case = time.time() + driver = selenium_chrome_webdriver.Chrome( + executable_path=chrome_driver_path, + options=chrome_options, + ) + return driver, time_before_run_use_case + + +old_windows = [] + + +def get_linux_chrome_web_driver(): + """Function to get Chrome web driver for Linux platform. + + Returns: + Linux Chrome web driver. + """ + LOGGER.info("executing get_linux_chrome_web_driver method.") + chrome_options = selenium_chrome_webdriver.ChromeOptions() + chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") + chrome_options.add_argument("--incognito") + chrome_options.add_argument("--disable-application-cache") + chrome_options.add_argument("--disable-extensions") + chrome_options.add_argument(f"--user-agent={get_random_user_agent()}") + time_before_run_use_case = time.time() + driver = selenium_chrome_webdriver.Chrome( + executable_path=DriversAndAppsPaths.LINUX_CHROME_WEB_DRIVER_PATH, + options=chrome_options, + ) + global old_windows + old_windows = driver.window_handles + # Open a new window for running tests + driver.execute_script("chrome.windows.create({incognito:true})") + test_window = [w for w in driver.window_handles if w not in old_windows][0] + driver.switch_to.window(test_window) + return driver, time_before_run_use_case + + +def get_windows_edge_web_driver(): + """Function to get Edge web driver for Windows platform. + + Returns: + Windows Edge web driver. + """ + LOGGER.info("executing get_windows_edge_web_driver method.") + from msedge.selenium_tools import Edge, EdgeOptions + + edge_options = EdgeOptions() + edge_options.use_chromium = True + edge_options.add_argument("inprivate") + edge_options.add_argument("--disable-application-cache") + edge_options.add_argument("--disable-extensions") + edge_options.add_argument(f"--user-agent={get_random_user_agent()}") + from webdriver_manager.microsoft import EdgeChromiumDriverManager + + edge_driver_path = EdgeChromiumDriverManager().install() + time_before_run_use_case = time.time() + driver = Edge( + executable_path=edge_driver_path, + options=edge_options, + ) + return driver, time_before_run_use_case + + +def get_native_app_driver(process_name): + """Function used to determined which native app driver should be used + based on OS type. + + Args: + process_name: process_name of use case. + + Returns: + native app driver. + """ + LOGGER.info("executing get_native_app_driver method.") + if platform.system() == ProcessNames.WINDOWS_PLATFORM_TYPE: + return get_windows_native_app_driver(process_name) + return get_linux_native_app_driver(process_name) + + +def get_windows_native_app_driver(process_name): + """Function used to determined which native app driver should be used + for Windows OS type. + + Args: + process_name: process_name of use case. + + Returns: + Windows native app driver. + """ + LOGGER.info("executing get_windows_native_app_driver method.") + if ProcessNames.EXCEL in process_name: + desired_caps = { + "app": DriversAndAppsPaths.EXCEL_APP_PATH, + "appArguments": DriversAndAppsPaths.EXCEL_APP_ARGUMENTS, + } + elif ProcessNames.PPT in process_name: + desired_caps = { + "app": DriversAndAppsPaths.PPT_APP_PATH, + "appArguments": DriversAndAppsPaths.PPT_APP_ARGUMENTS, + } + elif ProcessNames.WORD in process_name: + desired_caps = { + "app": DriversAndAppsPaths.WORD_APP_PATH, + "appArguments": DriversAndAppsPaths.WORD_APP_ARGUMENTS, + } + elif ProcessNames.PHOTOS_APP in process_name: + desired_caps = {"app": DriversAndAppsPaths.WINDOWS_PHOTOS_APP_ID} + elif ProcessNames.WIN_GAME_BAR_APP in process_name: + return None, time.time() + else: + raise NotImplementedError("other windows native app not supported yet.") + from appium import webdriver as appium_webdriver + + time_before_run_use_case = time.time() + driver = appium_webdriver.Remote( + command_executor=UseCasesURLS.WIN_APP_DRIVER_URL_PATH, + desired_capabilities=desired_caps, + ) + return driver, time_before_run_use_case + + +def get_linux_native_app_driver(process_name): + """Function used to determined which native app driver should be used + for Linux OS type. + + Args: + process_name: process_name of use case. + + Returns: + Linux native app driver. + + Raises: + NotImplementedError because it is not supported yet. + """ + raise NotImplementedError("get_linux_native_app_driver not supported yet.") + + +def get_random_user_agent(): + """Function used to get user agent with random uuid based on + platform type for browser use cases. + + Returns: + User agent with random uuid. + """ + LOGGER.info("executing get_random_user_agent method.") + if platform.system() == ProcessNames.WINDOWS_PLATFORM_TYPE: + user_agent = UserAgent.WINDOWS_USER_AGENT + else: + user_agent = UserAgent.LINUX_USER_AGENT + user_agent += " " + str(uuid.uuid4()) + return user_agent + + +def print_time_results(result, time_before_run_use_case): + """Function to print time results collected during running the use case. + + Args: + result: The results returned by executing the use case. + time_before_run_use_case: the time before run the use case. + """ + LOGGER.info("executing print_time_results method.") + time_after_run_use_case = time.time() + period_of_login_time = 0 + human_delay = 0 + if result is not None and isinstance(result, dict): + if result.get("period_of_login_time") is not None: + period_of_login_time = result.get("period_of_login_time") + if result.get("human_delay") is not None: + human_delay = result.get("human_delay") + result_time = time_after_run_use_case - time_before_run_use_case + result_time += human_delay + result_time -= period_of_login_time + execution_times = time_benchmark.execution_times + if len(execution_times) > 0: + for exec_time in execution_times: + RESULTS_LOGGER.info( + f"Time for execute function = {exec_time[0]} = {exec_time[1]}" + ) + RESULTS_LOGGER.info(f"The time for running this use case = {result_time}") + RESULTS_LOGGER.info(f"{15 * '-'}**********{15 * '-'}") + + +def close_used_linux_browser_windows(driver): + """Function to close used linux browser windows. + + Args: + driver: The WebDriver instance used to interact with + the browser. + """ + for w in driver.window_handles: + if w not in old_windows: + try: + driver.switch_to.window(w) + driver.close() + except NoSuchWindowException: + pass + + +def setup( + is_browsing: bool = False, + process_name: tuple = None, + is_execution_part: bool = False, +): + """ + Helper function that's responsible for determining the driver + to use corresponding if it is chrome or edge or native platform + application on both environment windows and linux. + + Args: + is_browsing: It should be True when the test will run on browser. + process_name: Tuple of process name. + is_execution_part: Indicates if the decorator ran for execute + part to allow calculation of it's time + """ + + def decorator(func): + def wrapper(): + LOGGER.info( + "start executing setup decorator with " + f"is_browsing = {is_browsing} and " + f"process_name = {process_name} and " + f"is_execution_part = {is_execution_part}." + ) + if process_name is None or len(process_name) == 0: + raise BenchmarkingException("process_name should not be empty.") + if is_browsing: + # browser app + driver, time_before_run_use_case = get_browser_web_driver( + process_name + ) + else: + # native app + driver, time_before_run_use_case = get_native_app_driver( + process_name + ) + # invoke the use case + try: + if driver is not None: + result = func(driver) + else: + result = func() + finally: + if driver is not None: + if platform.system() != ProcessNames.WINDOWS_PLATFORM_TYPE: + close_used_linux_browser_windows(driver) + else: + driver.quit() + if is_execution_part: + print_time_results(result, time_before_run_use_case) + LOGGER.info("ending of execute setup decorator.") + return result + + return wrapper + + return decorator
diff --git a/contrib/cros_ca/benchmarking/process_measurements.py b/contrib/cros_ca/benchmarking/process_measurements.py new file mode 100644 index 0000000..9d8b94f --- /dev/null +++ b/contrib/cros_ca/benchmarking/process_measurements.py
@@ -0,0 +1,205 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable = R0902, W1203 +""" +Script contain ProcessMeasurements class. +""" + +import time + +import psutil +from psutil import NoSuchProcess + +from common.common_utility import remove_multiple_from_list +from common.constants import LoggingConfig +from common.iqr import calculate_iqr_outliers + +RESULTS_LOGGER = LoggingConfig.get_logger(False) + + +class ProcessMeasurements: + """ProcessMeasurements class that responsible to handle + measurements of process. + """ + + def __init__(self, proces: psutil.Process): + """Constructor of ProcessMeasurements class.""" + self._proces = proces + self._proces_id = proces.pid + self._proces_name = proces.name() + self._cpu_percent = [] + self._memory_percent = [] + self._user_cpu_times = [] + self._system_cpu_times = [] + self._last_sys_cpu_times = None + self._last_proc_cpu_times = None + self.average_dict = None + + def appends_process_measurements(self): + """Method to take measurements of proces and appends them + to the instance variable lists. + """ + proces = self._proces + try: + if proces is not None and proces.is_running(): + cpu = self.calculate_cpu_percent() + mem = proces.memory_percent() + cpu_times = proces.cpu_times() + user_cpu_times = cpu_times.user + system_cpu_times = cpu_times.system + self._cpu_percent.append(cpu) + self._memory_percent.append(mem) + self._user_cpu_times.append(user_cpu_times) + self._system_cpu_times.append(system_cpu_times) + except NoSuchProcess: + pass + + def print_average_with_outlier(self): + """Method to print the average of instance variable lists after + pass the lists to calculate_iqr_outliers method to determine + the outliers value should be removed from original list before + calculating the average, and save those average in average_dict + dictionary to be used in Benchmarking class when print the summation. + """ + cpu = self._cpu_percent + mem = self._memory_percent + user_cpu_times = self._user_cpu_times + system_cpu_times = self._system_cpu_times + process_name = self._proces_name + process_id = self._proces_id + + if len(cpu) > 0: + remove_multiple_from_list( + cpu, calculate_iqr_outliers(cpu)["Outliers"] + ) + if len(mem) > 0: + remove_multiple_from_list( + mem, calculate_iqr_outliers(mem)["Outliers"] + ) + if len(user_cpu_times) > 0: + remove_multiple_from_list( + user_cpu_times, + calculate_iqr_outliers(user_cpu_times)["Outliers"], + ) + if len(system_cpu_times) > 0: + remove_multiple_from_list( + system_cpu_times, + calculate_iqr_outliers(system_cpu_times)["Outliers"], + ) + + average_dict = { + "cpu_percent": sum(cpu) / len(cpu) if len(cpu) > 0 else -1, + "memory_percent": sum(mem) / len(mem) if len(mem) > 0 else -1, + "user_cpu_times": ( + sum(user_cpu_times) / len(user_cpu_times) + if len(user_cpu_times) > 0 + else -1 + ), + "system_cpu_times": ( + sum(system_cpu_times) / len(system_cpu_times) + if len(system_cpu_times) > 0 + else -1 + ), + } + self.average_dict = average_dict + RESULTS_LOGGER.info( + f"average results for process name = {process_name} and pid = {process_id} is: " + ) + RESULTS_LOGGER.info( + f"cpu percent = ({average_dict.get('cpu_percent')}) %" + ) + RESULTS_LOGGER.info( + f"memory percent = ({average_dict.get('memory_percent')}) %" + ) + RESULTS_LOGGER.info( + f"user cpu time = ({average_dict.get('user_cpu_times')}) seconds" + ) + RESULTS_LOGGER.info( + f"system cpu time = ({average_dict.get('system_cpu_times')}) seconds" + ) + + RESULTS_LOGGER.info(f"{15*'-'}**********{15*'-'}") + + def calculate_cpu_percent(self): + """Return a float representing the current process CPU + utilization as a percentage. + it compares process times to system CPU times elapsed + since last call, returning immediately (non-blocking). + That means that the first time this is called it will + return a meaningful 0.0 value. + + Returns: + Current process CPU utilization as a percentage. + """ + num_cpus = psutil.cpu_count() or 1 + + def timer(): + _timer = getattr(time, "monotonic", time.time) + return _timer() * num_cpus + + st1 = self._last_sys_cpu_times + pt1 = self._last_proc_cpu_times + st2 = timer() + pt2 = self._proces.cpu_times() + if st1 is None or pt1 is None: + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + return 0.0 + delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system) + delta_time = st2 - st1 + # reset values for next call in case of interval == None + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + + try: + # This is the utilization split evenly between all CPUs. + # E.g. a busy loop process on a 2-CPU-cores system at this + # point is reported as 50% instead of 100%. + overall_cpus_percent = (delta_proc / delta_time) * 100 + except ZeroDivisionError: + return 0.0 + + single_cpu_percent = overall_cpus_percent * num_cpus + return round(single_cpu_percent, 1) + + def __eq__(self, other): + """Overloading of equal method for ProcessMeasurements class + to make the set comparison depends on the proces rather + than all instance variable in the class. + + Args: + other: Other ProcessMeasurements instance. + + Returns: + True if ProcessMeasurements proces isinstance or equal + to other ProcessMeasurements proces, else false. + """ + if not isinstance(other, ProcessMeasurements): + return False + return self._proces == other._proces + + def __hash__(self): + """Overloading of hash method for ProcessMeasurements class + to make the set comparison depends on the proces rather + than all instance variable in the class. + + Returns: + Hash of ProcessMeasurements proces. + """ + return hash(self._proces) + + def __repr__(self): + """Overloading of representation method for ProcessMeasurements class. + + Returns: + Specific format of ProcessMeasurements class when it needs to + be presented. + """ + return ( + f"( proces = {self._proces} \n " + + f"cpu_percent = {self._cpu_percent} \n" + + f"memory_percent = {self._memory_percent} \n " + + f"user_cpu_times = {self._user_cpu_times} \n" + + f"system_cpu_times = {self._system_cpu_times})" + )
diff --git a/contrib/cros_ca/benchmarking/time_benchmark.py b/contrib/cros_ca/benchmarking/time_benchmark.py new file mode 100644 index 0000000..ac5a17a --- /dev/null +++ b/contrib/cros_ca/benchmarking/time_benchmark.py
@@ -0,0 +1,40 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Script contains decorator to measure the time per some function.""" +import inspect +import time + +execution_times = [] + + +def time_measure(special_arg_index: int = None): + """Helper decorator used to calculate time used by specific + execution function that annotated with it. + + Args: + special_arg_index: Index of function argument passed + that should be presents in collected time. + """ + + def decorator(func): + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + callback_stack = inspect.stack() + for i, _ in enumerate(callback_stack): + if "execute" in callback_stack[i][3]: + execution_time = end_time - start_time + special_arg = "" + if special_arg_index is not None: + special_arg = " for " + args[special_arg_index] + execution_times.append( + (func.__name__ + special_arg, execution_time) + ) + break + return result + + return wrapper + + return decorator
diff --git a/contrib/cros_ca/common/__init__.py b/contrib/cros_ca/common/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/common/__init__.py
diff --git a/contrib/cros_ca/common/common_utility.py b/contrib/cros_ca/common/common_utility.py new file mode 100644 index 0000000..3e132fb --- /dev/null +++ b/contrib/cros_ca/common/common_utility.py
@@ -0,0 +1,71 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +""" +Scripts contain some common utility helper function. +""" +import os +from datetime import datetime + +from benchmarking.benchmarking_exception import BenchmarkingException + +CURRENT_DIR_PATH = os.path.dirname(os.path.abspath(__file__)) + + +def remove_multiple_from_list(lst1: list, lst2: list): + """Function used to remove multiple values exist in list from + another list. + """ + for element in lst2: + if element in lst1: + lst1.remove(element) + + +def check_out_dir_creation_required(): + """Function to check output with time directory that contains + logs and results if it exists or not, if not it will be created. + + Returns: + The path of output with time directory. + """ + current_time = datetime.now().strftime("%Y%m%d-%H%M%S") + out_dir_path = os.path.join(CURRENT_DIR_PATH, f"../out/{current_time}") + if not os.path.exists(out_dir_path): + os.makedirs(out_dir_path) + return out_dir_path + + +WEB_FILE_DIR_PATH = os.path.join(CURRENT_DIR_PATH, "../resources/web_app_files") + + +def get_file_path_to_upload(new_doc_name, ext_type): + """Function to return absolute file path needs to be uploaded + from local storge after rename existing file to uuid4. + + Args: + new_doc_name: The new name of document that be represented by uuid4 + with file type extension. + ext_type: Office file extension type. + + Returns: return absolute file path needs to be uploaded from local storge. + + Raises: + BenchmarkingException: in case no file with provided extension found. + """ + files_name = os.listdir(WEB_FILE_DIR_PATH) + for file_name in files_name: + if file_name.endswith(ext_type): + file_name_path = os.path.abspath( + WEB_FILE_DIR_PATH + rf"/{file_name}" + ) + new_doc_name_path = os.path.abspath( + WEB_FILE_DIR_PATH + rf"/{new_doc_name}" + ) + os.rename(file_name_path, new_doc_name_path) + break + else: + raise BenchmarkingException( + f"Cannot find any file ends with extension type = {ext_type}" + f" in WEB_FILE_DIR_PATH = {WEB_FILE_DIR_PATH}" + ) + return new_doc_name_path
diff --git a/contrib/cros_ca/common/constants.py b/contrib/cros_ca/common/constants.py new file mode 100644 index 0000000..9062c0b --- /dev/null +++ b/contrib/cros_ca/common/constants.py
@@ -0,0 +1,211 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# pylint: disable = too-few-public-methods +"""Global Constants values.""" +import getpass +import logging +import os + +from dotenv import load_dotenv + +from common.common_utility import check_out_dir_creation_required + +CURRENT_OS_USER_NAME = getpass.getuser() +CURRENT_DIR_PATH = os.path.dirname(os.path.abspath(__file__)) +load_dotenv(os.path.join(CURRENT_DIR_PATH, "../.env")) + + +class DriversAndAppsPaths: + """This class contains the drivers paths in the local machine to use them + in the automation code. + """ + + LINUX_CHROME_WEB_DRIVER_PATH = r"/usr/local/chromedriver/chromedriver" + WIN_APP_DRIVER_PATH = os.getenv("WIN_APP_DRIVER_PATH") + EXCEL_APP_PATH = ( + r"C:\Program Files\Microsoft Office\root\Office16\EXCEL.exe" + ) + EXCEL_APP_ARGUMENTS = os.path.join( + CURRENT_DIR_PATH, "../resources/native_app_files/CarsExample.xlsx" + ) + + WORD_APP_PATH = ( + r"C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE" + ) + WORD_APP_ARGUMENTS = os.path.join( + CURRENT_DIR_PATH, "../resources/native_app_files/HelloWorld.docx" + ) + + PPT_APP_PATH = ( + r"C:\Program Files\Microsoft Office\root\Office16\POWERPNT.EXE" + ) + PPT_APP_ARGUMENTS = os.path.join( + CURRENT_DIR_PATH, "../resources/native_app_files/Benchmarking.pptx" + ) + + XML_WEBSITES_URL_PATH = os.path.join( + CURRENT_DIR_PATH, "../resources/browse_websites_by_url.xml" + ) + + WINDOWS_PHOTOS_APP_ID = "Microsoft.Windows.Photos_8wekyb3d8bbwe!App" + + TEST_PHOTO_URL_DIR_PATH = os.path.join( + CURRENT_DIR_PATH, "../resources/native_app_files" + ) + + TEST_PHOTO_NAME = "TestImage.png" + + WINDOWS_VIDEOS_CAPTURES_DIR_PATH = ( + rf"C:\Users\{CURRENT_OS_USER_NAME}\Videos\Captures" + ) + + +class PageLoadConfig: + """This class stores configuration constants related to page load times + and timeouts.""" + + MAX_PAGE_LOAD_TIME = 100 + WAIT_TIMEOUT = 50 + + +class ProcessNames: + """This class contains the process names and types for the OSs and the + applications used in the automation code. + """ + + PROCESS_NAME_TO_BE_EXCLUDE = ( + "chrome_crashpad_handler", + "chromedriver", + "msedgedriver", + "msedge_crashpad", + "msedge_crashpad_handler", + ) + EXCEL = "EXCEL.EXE" + PPT = "POWERPNT.EXE" + WORD = "WINWORD.EXE" + AI_PPT = "ai.exe" + PHOTOS_SERVICE = "PhotosService.exe" + PHOTOS_APP = "PhotosApp.exe" + WIN_GAME_BAR_APP = "GameBar.exe" + WIN_GAME_BAR_SERVER_APP = "GameBarFTServer.exe" + WINDOWS_CHROME = "chrome.exe" + LINUX_CHROME = "chrome" + WINDOWS_EDGE = "msedge.exe" + WINDOWS_PLATFORM_TYPE = "Windows" + CHROME_BROWSER_TYPE = "chrome" + EDGE_BROWSER_TYPE = "edge" + + +class TestingAccountLicense: + """This class contains the license for the account that will be used for + testing. + """ + + LOGIN_ACCOUNT_EMAIL = os.getenv("LOGIN_ACCOUNT_EMAIL") + LOGIN_ACCOUNT_PASSWORD = os.getenv("LOGIN_ACCOUNT_PASSWORD") + + +class UseCasesURLS: + """This class contains the required URLS to be used by the use cases in + the automation code. + """ + + GOOGLE_DRIVE_URL_PATH = "https://drive.google.com/drive/my-drive" + MS_DRIVE_URL_PATH = "https://onedrive.live.com/?id=root" + WIN_APP_DRIVER_URL_PATH = "http://127.0.0.1:4723" + PDF_WEB_URL_PATH = "file:///" + os.path.join( + CURRENT_DIR_PATH, "../resources/native_app_files/HelloWorld.pdf" + ) + + +class DocumentAccountType: + """This class contains constants used in account + log in operation to differentiate if you should + log in to google or microsoft account. + """ + + GOOGLE = "Google" + MICROSOFT = "Microsoft" + + +class HumanDelay: + """This class contains constants used to be added to total time for + some use case as human delay when human try to simulate running + the use case by hand. + """ + + EXPECTED_CLICK_OR_ACTION_TIME = 0.150 + GOOGLE_EXCEL_WEB = 22 * EXPECTED_CLICK_OR_ACTION_TIME + GOOGLE_PPT_WEB = 7 * EXPECTED_CLICK_OR_ACTION_TIME + GOOGLE_WORD_WEB = 9 * EXPECTED_CLICK_OR_ACTION_TIME + MICROSOFT_EXCEL_WEB = 19 * EXPECTED_CLICK_OR_ACTION_TIME + MICROSOFT_PPT_WEB = 8 * EXPECTED_CLICK_OR_ACTION_TIME + MICROSOFT_WORD_WEB = 5 * EXPECTED_CLICK_OR_ACTION_TIME + + +class UserAgent: + """This class contains constants value for user agents + based on platform.""" + + WINDOWS_USER_AGENT = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/121.0.0.0 Safari/537.36" + ) + LINUX_USER_AGENT = ( + "Mozilla/5.0 (X11; Linux x86_64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/121.0.0.0 Safari/537.36" + ) + + +class LoggingConfig: + """Class contains Logging configuration.""" + + TRACES_FORMAT = "%(asctime)s.%(msecs)03d %(levelname)s| %(filename)s:%(lineno)s| %(message)s" + RESULTS_FORMAT = "%(asctime)s - %(message)s" + DATE_FORMAT = "%m/%d %H:%M:%S" + TRACES_FILE = "/logs.log" + RESULTS_FILE = "/results.log" + LOGS_FILE_DIR_PATH = check_out_dir_creation_required() + + @classmethod + def get_logger(cls, is_traces_log: bool = True): + """Method used to get the logger with specific configuration. + The Logger have 2 configuration: + 1. traces logger configuration for printing traces logs of code. + 2. results logger configuration for printing the results of + benchmarking. + + Args: + is_traces_log: Indicate if returned logger should be + traces or results logger. + """ + name = "traces" if is_traces_log else "results" + logger = logging.getLogger(name) + if not logger.hasHandlers(): + if is_traces_log: + log_file_path = ( + LoggingConfig.LOGS_FILE_DIR_PATH + LoggingConfig.TRACES_FILE + ) + formatter = logging.Formatter( + fmt=LoggingConfig.TRACES_FORMAT, + datefmt=LoggingConfig.DATE_FORMAT, + ) + else: + log_file_path = ( + LoggingConfig.LOGS_FILE_DIR_PATH + + LoggingConfig.RESULTS_FILE + ) + formatter = logging.Formatter( + fmt=LoggingConfig.RESULTS_FORMAT, + datefmt=LoggingConfig.DATE_FORMAT, + ) + + handler = logging.FileHandler(log_file_path) + handler.setFormatter(formatter) + logger.setLevel(logging.INFO) + logger.addHandler(handler) + + return logger
diff --git a/contrib/cros_ca/common/iqr.py b/contrib/cros_ca/common/iqr.py new file mode 100644 index 0000000..1fcedee --- /dev/null +++ b/contrib/cros_ca/common/iqr.py
@@ -0,0 +1,62 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Find the outliers using IQR formula.""" + +import numpy as np + + +def calculate_iqr_outliers(use_case_results: list): + """ + Calculate Inter quartile Range (IQR) and identify outliers in a dataset. + + This function takes a list of numerical values and calculates the first + and third quartiles to find the IQR and the outliers. + + Args: + use_case_results (list): A list of numerical values for which to + calculate IQR and identify outliers. + + Returns: + dict: A dictionary containing the following key-value pairs: + - "Q1": The first quartile. + - "Q3": The third quartile. + - "IQR": The Inter quartile Range. + - "Lower Bound": The lower bound for identifying outliers. + - "Upper Bound": The upper bound for identifying outliers. + """ + + data_array = np.array(use_case_results) + sorted_data = np.sort(data_array) + q1_index = int(0.25 * (len(sorted_data))) + q3_index = int(0.75 * (len(sorted_data))) + + if len(sorted_data) % 2 == 0: + q1 = (sorted_data[q1_index] + sorted_data[q1_index - 1]) / 2 + q3 = (sorted_data[q3_index] + sorted_data[q3_index - 1]) / 2 + else: + q1 = sorted_data[q1_index] + q3 = sorted_data[q3_index] + + # Calculate the IQR value + iqr = q3 - q1 + + # Calculate lower and upper bounds for outliers + lower_bound = q1 - (1.5 * iqr) + upper_bound = q3 + 1.5 * iqr + outliers = (data_array < lower_bound) | (data_array > upper_bound) + + return { + "Q1": q1, + "Q3": q3, + "IQR": iqr, + "Lower Bound": lower_bound, + "Upper Bound": upper_bound, + "Outliers": data_array[outliers].tolist(), + } + + +if __name__ == "__main__": + # Example + use_case_sample_result = [11, 31, 21, 19, 8, 54, 35, 26, 23, 13, 29, 17] + result = calculate_iqr_outliers(use_case_sample_result)
diff --git a/contrib/cros_ca/remote_testing/README.md b/contrib/cros_ca/remote_testing/README.md new file mode 100644 index 0000000..7fe51e9 --- /dev/null +++ b/contrib/cros_ca/remote_testing/README.md
@@ -0,0 +1,183 @@ +# Remote end-to-end testing + +## Description + +This package contains code for running an automation test on a remote +machine (DUT) from a Linux server. The system comprises two main components: a +server agent and a client agent. These agents communicate over OpenSSH (Secure +Shell) and move files between them using SFTP (SSH File Transfer Protocol). The +server initiates file transfers to the client and instructs it to execute an +automation code. The client executes the provided code and generate results +that the server will get and save them back. + +The Remote end-to-end testing also contain the option to run the test from +the sleep mode on the Windows OS for now, where the server agent will send +wake on lan signal to the client to make it wake up and complete execution. + +- <b>Server agent</b>: This agent will be executed on the host machine. It is + responsible for: + - Accept the required arguments to connect with the DUT and specify the + automation code to be executed on the DUT. + - Send the required files and directories for running the automation code to + the DUT using SFTP protocol. + - Send a request over SSH to run the automation test on the DUT device. + - Get the test results from the DUT device.<br><br> + + +- <b>Client agent</b>: This agent will be executed on the DUT device to + perform the + required actions on it. It is responsible for several tasks, such as: + - Keep listening until a request received from the server agent over SSH + protocol. + - Install the required packages using the requirements file from the + server agent. + - Run the automation test. + - Save the agent and test results. + - Remove the files and directories originating from the host machine, + as well as any generated files from the client agent. + +<b style="color:yellow">Note</b>: You can use the scripts provided in ```/remote_testing/setup_scripts``` that reduce +the manual effort in +some steps, please refer to [README.md](setup_scripts%2FREADME.md) for more description. + +## Prerequisites + +### Prerequisites for Server (Host) Machine + +The host machine intended to run the ```server_agent.py``` script should have +the +Linux (Ubuntu) operating system installed. Below are the steps that should be +completed before executing the server agent: + +1. Python installed on the host machine. +2. The code of the automation tests ```cros_ca``` and its content should be + existed in the host machine. +3. ```server_agent.py``` should be moved to the home + directory:```/home/user_name/server_agent.py```. + +### Prerequisites for Client (DUT) Machine + +#### Client Machine With Windows OS: + +<b style="color:yellow">Note</b>: the first two steps covered by ```wi_dut_setup``` scripts, if you want to know more +please refer to [README.md](setup_scripts%2FREADME.md) file that describe the scripts. + +1. OpenSSH Server should be installed on the Windows machine: + - Open the device <b>Settings -> System -> Optional features -> Add an + optional feature</b>. + - Search for <b>OpenSSH Server</b> and Install it. + +2. OpenSSH Server should be start running to accept signals over SSH, this + can be done by following the below steps: + - Search for <b>Services</b> in the taskbar search box. + - Find <b>OpenSSH SSH Server-> right click -> properties</b> change the + Startup type to Automatic (To make it started automatically when open the + device). + - Apply and press <b>Start</b> then <b>OK</b>. +3. Now you need to follow the steps in ([README.md](..%2FREADME.md)) file + under <b> + Prerequisites for Windows </b> title. + +4. Update the ```PYTHONPATH``` in the system environment variables by replacing + its value + with ```C:\Users\your_username\Automation Code```. + +5. Copy the ```client_agent.py``` script to your user where the path of the + agent will be like this: ```C:\Users\your_username\client_agent.py```. + +6. For <b style="color:blue"> Sleep scenario </b> that is supported for + windows you can follow + this [[Sleep Mode Scenario](#sleep-mode)] + +#### Client Machine With Chrome OS: + +1. For the Chrome OS to enable OpenSSH testing image installation required, + so please follow the steps in this ([README.md](..%2FREADME.md)) file + under <b> + Prerequisites for ChromeOS </b> title. +2. Update the ```PYTHONPATH``` in the bashrc file by replacing its value + with ```/home/chronos/user/MyFiles/Automation Code``` where this directory + will + contain the code that will be moved by the server agent. +3. Copy the ```client_agent.py``` script to ```/home/chronos/user/```. + +4. It will be better to restart the device after doing the above steps. + +### Usage + +1. You need to run the ```client_agent.py``` on the DUT first, this can be + done as below: + - For Windows: open the cmd in the client_agent.py directory, then: + ``` + python client_agent.py + ``` + - For Chrome: open the shell terminal and navigate to + ```/home/chronos/user```, then: + ``` + python3 client_agent.py + ``` +2. Run the ```server_agent.py``` on the server machine and pass the + arguments: + ``` + Arguments Descriptions: + + DUT_IP: The IP address of the DUT machine. + DUT_MAC_ADDRESS: The mac address of the DUT machine + DUT_username: The username of the DUT machine for authentication. + DUT_password: The password of the DUT machine for authentication. + status: An optional argument to specifiy from which status to start the + test (sleep or current) mode. + test_path: Absolute path for the automation file + ``` + + - Run with the required arguments: + ``` + python3 server_agent.py DUT_IP DUT_MAC_ADDRESS DUT_username + DUT_password "test_abs_path" + ``` + - Run with all arguments: + ``` + python3 server_agent.py DUT_IP DUT_MAC_ADDRESS DUT_username + DUT_password --status STATUS_VALUE "test_abs_path" + ``` + +### <a id="sleep-mode"></a>Sleep Mode Scenario + +This scenario is currently supported on Windows client machines. It involves +running a test after forcing the device to enter sleep mode. Subsequently, the +server agent sends a signal to awaken the device and execute the automation +test. To enable this scenario, adhere to the following prerequisites: + +1. The DUT device must be connected to the network through ethernet cable, + and you can use ethernet adapter. +2. Change the network adapter settings to enable it to receive magic puckets: + - Open the <b> Control Panel -> Network and Internet -> Network and + Sharing Center</b> + - Select <b> Change adapter settings</b> option. + - Right-click on the ethernet adapter <b> properties -> Configure</b> + - In the <b>Advanced</b> window from the Property choices change the + value of <b>Wake On Magic Packet and Wake on pattern match</b> to <b> + Enabled </b>. +3. Open the BIOS settings and enable Wake On Lan. +4. Disable sign in for sleep mode,this can be done by following the below + steps: + - Search for <b>Sign-in options</b> in the taskbar search box. + - In the <b> Additional Settings </b> or <b> Require sign-in </b> change + the value of <b> If you've been away, when should Windows require you + to sign in again?</b> to <b>Never</b> + +#### Sleep Scenario Execution: + +In the server side you need to add a new argument which is ```--status sleep``` +before the test file path, the command will be as below: + + ``` + python3 server_agent.py DUT_IP DUT_MAC_ADDRESS DUT_username + DUT_password --status sleep "test_abs_path" + ``` + +where the ```--status``` argument is an optional argument, and it's default +behavior is to run the automation test from the current mode. + +In this scenario the ```--status sleep``` and the ```DUT_MAC_ADDRESS``` will +be used to make the device wake up from the sleep mode. \ No newline at end of file
diff --git a/contrib/cros_ca/remote_testing/__init__.py b/contrib/cros_ca/remote_testing/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/cros_ca/remote_testing/__init__.py
diff --git a/contrib/cros_ca/remote_testing/client_agent.py b/contrib/cros_ca/remote_testing/client_agent.py new file mode 100644 index 0000000..e78e976 --- /dev/null +++ b/contrib/cros_ca/remote_testing/client_agent.py
@@ -0,0 +1,316 @@ +# pylint: disable= R1710,W1203, W0612, W0718 +"""Client agent for the DUT machine to run test and connect with the server +agent""" + +import argparse +import ctypes +import logging +import os +import shlex +import socket +import subprocess +import shutil +import time +import platform + +SYSTEM = platform.system().lower() +IN_PROGRESS_RESULTS_FILE = "in_progress_results.txt" +RESULTS_FILE = "DUT_automation_results.txt" + + +def logging_definition(): + """Configure logging to write logs to a file.""" + logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" + ) + + +def logging_setup(): + """Setup file handler to store the logs inside it. + + Returns: + logging file handler. + """ + results_file_handler = logging.FileHandler(IN_PROGRESS_RESULTS_FILE) + results_file_handler.setLevel(logging.INFO) + results_file_handler.setFormatter( + logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + ) + logging.getLogger().addHandler(results_file_handler) + return results_file_handler + + +def search_file_recursive(directory, file_name): + """Searches for a file within a directory and its subdirectories. + + Args: + directory: The directory to search within. + file_name: The name of the file to search for. + + Returns: + path of the file within the directory. + """ + try: + for root, dirs, files in os.walk(directory): + if file_name in files: + return os.path.join(root, file_name) + raise FileNotFoundError( + f"Error! Cannot find {file_name} in {directory} " f"directory" + ) + except FileNotFoundError as e: + logging.error(e) + + except Exception as e: + logging.error(e) + + +def install_requirements_file(code_directory): + """Helper function to install the contents of the requirements.txt file. + + Args: + code_directory: The directory that contains the requirements.txt. + """ + try: + requirements_file_path = search_file_recursive( + code_directory, "requirements.txt" + ) + logging.info("Installing the requirements file") + if SYSTEM == "linux": + command = f'python3 -m pip install -r "{requirements_file_path}"' + subprocess.run( + command, check=True, capture_output=True, text=True, shell=True + ) + else: + command = f'pip install -r "{requirements_file_path}"' + subprocess.run( + command, shell=True, check=True, capture_output=True, text=True + ) + + logging.info("Finish installing the requirements file successfully") + + except subprocess.CalledProcessError as e: + logging.error(f"Error while installing the requirements file: {e}") + except FileNotFoundError as e: + logging.error(e) + + +def run_automation_file(automation_code_directory, automation_file): + """This function is responsible for running the use case code by + executed its logic. + + Args: + automation_code_directory: The name of the directory that contains the + code. + automation_file: The path of the automation file that contains the use + case logic. + """ + try: + automation_file_path = search_file_recursive( + automation_code_directory, automation_file + ) + full_path = automation_file_path + if SYSTEM == "linux": + run_command = ["python3", full_path] + subprocess.run(run_command, check=True) + else: + run_command = ["python", full_path] + subprocess.run(run_command, shell=True, check=True) + + except subprocess.CalledProcessError as e: + logging.error(f"Error while running the automation file: {e}") + except FileNotFoundError as e: + logging.error(f"Error! Cannot find the automation file: {e}") + + +def extract_command_values(command_string): + """Helper function to extract the arguments from the command string. + + Args: + command_string: The command received from the controller machine. + + Returns: + A tuple containing the status, remote directory, and file name + extracted from the command string. + """ + parser = argparse.ArgumentParser() + parser.add_argument("--status", required=True) + parser.add_argument("-d", "--remote_directory", required=True) + parser.add_argument("-f", "--file_name", required=True) + input_list = shlex.split(command_string) + args = parser.parse_args(input_list) + return args.status, args.remote_directory, args.file_name + + +def delete_file(file_name): + """Helper function to delete a given file from the local machine. + + Args: + file_name: The name of the file to be deleted. + """ + if os.path.exists(file_name): + os.remove(file_name) + logging.info(f"File {file_name} deleted successfully") + else: + logging.warning(f"Results file '{file_name}' not found") + + +def teardown_automation(automation_code_directory): + """ + Teardown function to clean up resources after automation execution. + + Args: + automation_code_directory (str): Path to the directory containing + automation code. + """ + try: + # Remove the automation code directory + if os.path.exists(automation_code_directory): + logging.info("Deleting Automation Code directory") + shutil.rmtree(automation_code_directory) + logging.info("Successfully deleted Automation Code directory") + else: + logging.warning( + f"Automation Code directory '{automation_code_directory}' not found" + ) + time.sleep(3) + delete_file(RESULTS_FILE) + + except Exception as e: + logging.error(f"Error occurred during teardown: {e}") + + +def set_system_suspend_state(): + """Helper function to put the device in the sleep mode. + + This function will simulate the process that we execute it when sleep + choice selected from the start menu. + """ + system_power_state = ctypes.c_int + suspend = 0 + ctypes.windll.powrprof.SetSuspendState(system_power_state(suspend), 0, 0) + + +def read_file_content(file_path): + """Helper function to read the content of a given file path. + + Args: + file_path: the path of the required file to be read. + + Returns: + The content of the given file. + """ + try: + with open(str(file_path), "r", encoding="utf-8") as file: + return file.readlines() + except FileNotFoundError: + return ["File not found."] + + +def append_to_results_file(file_name, code_directory): + """Helper function to append the content of a given file to the results + file. + + Args: + file_name : The name of the file to be appended. + code_directory: The directory of the automation tests. + """ + full_path = search_file_recursive(rf"{code_directory}/out", file_name) + file_content = read_file_content(full_path) + with open(IN_PROGRESS_RESULTS_FILE, "a", encoding="utf-8") as file: + file.writelines(file_content) + + +def rename_file(): + """Helper function to rename a specific file.""" + try: + os.rename(IN_PROGRESS_RESULTS_FILE, RESULTS_FILE) + except FileNotFoundError: + print(f"File {IN_PROGRESS_RESULTS_FILE} not found") + except FileExistsError: + print(f"File {RESULTS_FILE} already exists") + + +def main(): + """The entry point of the target agent that will: + 1. Set up the logging and logging file handler. + 2. Listen to the SSH request from the server machine. + 3. Receive command from the server machine. + 4. Install the requirements file. + 5. Run the use case automation test based on the received status. + 6. Delete the files and directories for the automation test. + """ + + host = "" + socket_port = 8888 + logging_definition() + logs_file_handler = logging_setup() + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket: + server_socket.bind((host, socket_port)) + server_socket.listen() + + logging.info(f"Server is listening on port {socket_port}") + + while True: + conn, addr = server_socket.accept() + with conn: + + logging.info(f"Connected by {addr}") + server_command = conn.makefile().readline().strip() + logging.info("Received command from the server agent.") + + if ("--status" and "-d" and "-f") in server_command: + status, code_directory, automation_file = ( + extract_command_values(server_command) + ) + + install_requirements_file(code_directory=code_directory) + + if status == "current": + logging.info( + f"Started test '{automation_file}' from the " + "current mode" + ) + run_automation_file(code_directory, automation_file) + logging.info(f"Finish executing {automation_file} test") + + elif status == "sleep": + logging.info( + f"Started test {automation_file} from sleep mode" + ) + logging.info("Entering sleep mode") + time_before_sleep = time.time() + set_system_suspend_state() + time_after_wakeup = time.time() + logging.info("Waking up from sleep mode") + logging.info(f"Started test {automation_file}") + run_automation_file(code_directory, automation_file) + time_from_wakeup_to_finish = ( + time.time() - time_after_wakeup + ) + time_from_sleep_to_finish = ( + time.time() - time_before_sleep + ) + logging.info( + "Time from wake up until finish the test = " + f"{time_from_wakeup_to_finish}" + ) + logging.info( + "Time from entering sleep mode until finish the " + f"test = {time_from_sleep_to_finish}" + ) + logging.info(f"Finish executing {automation_file} test") + + time.sleep(5) + + append_to_results_file("logs.log", code_directory) + append_to_results_file("results.log", code_directory) + logs_file_handler.close() + rename_file() + logs_file_handler.close() + teardown_automation(code_directory) + return "Finish DUT Automation Code" + + +if __name__ == "__main__": + main()
diff --git a/contrib/cros_ca/remote_testing/server_agent.py b/contrib/cros_ca/remote_testing/server_agent.py new file mode 100644 index 0000000..62d3ce1 --- /dev/null +++ b/contrib/cros_ca/remote_testing/server_agent.py
@@ -0,0 +1,512 @@ +# pylint: disable= W1203, W0718, R1710 +"""Server Agent For Remote Connection with a DUT.""" + +import argparse +import getpass +import logging +import os +import platform +import shutil +import socket +import subprocess +import sys +import tarfile +import time +import zipfile +import urllib.request +from datetime import datetime + +import paramiko +import pysftp +import requests + +SYSTEM = platform.system().lower() +AUTOMATION_RESULTS_FILE = "remote_connection_cuj_results.txt" +DUT_RESULTS_FILE = "DUT_automation_results.txt" +IN_PROGRESS_RESULTS_FILE = "in_progress_results.txt" +PRIVATE_KEY_PATH = os.path.expanduser("~/.ssh/testing_rsa") + + +def logging_definition(): + """Configure logging to write logs to a file.""" + logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" + ) + + +def logs_file_setup(): + """Setup file handler to store the logs inside it. + + Returns: + logging file handler. + """ + file_handler = logging.FileHandler(AUTOMATION_RESULTS_FILE) + file_handler.setLevel(logging.INFO) + file_handler.setFormatter( + logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + ) + logging.getLogger().addHandler(file_handler) + return file_handler + + +def get_user_input(): + """Helper function to get the user arguments + The arguments will be used to access the target (DUT) machine and do the + required processes. + + Returns: + Tuple: A tuple containing the following configuration parameters: + - target_ip: The IP address of the target (DUT) machine. + - target_mac_address: The mac address of the target (DUT) machine. + - username: The username for authentication. + - status: The status for the starting point of the test. + - test_path: Absolute path for the automation file. + """ + parser = argparse.ArgumentParser(description="Automation Script for Target Machine") + + # Add required command-line arguments + parser.add_argument("target_ip", help="Target machine IP address") + parser.add_argument("target_mac_address", help="Target machine mac address") + parser.add_argument("username", help="Target machine username") + + # Add an optional flag for automation running status + parser.add_argument( + "--status", + choices=["sleep"], + default="current", + help="Automation running status (current or sleep ) mode", + ) + + parser.add_argument("test_path", help="Absolute path of the automation " "file") + + try: + args = parser.parse_args() + return args + except SystemExit: + logging.error("Error: Missing required arguments.") + logging.error("Expected command:") + logging.error( + "python script.py <target_ip> <target_phy_address> <username> " + "[--status <status>] <test_path>" + ) + sys.exit(1) + except FileNotFoundError as e: + logging.error(e) + sys.exit(1) + + +def extract_path_prefix(full_path, keyword): + """Get the prefix for a given file path. + + Args: + full_path: The full path of the file or directory. + keyword: The word to search for in the path. + + Returns: + The prefix for the given path. + """ + return full_path.split(keyword)[0] + keyword + + +def push_files(test_path, remote_directory, sftp): + """Push the required files and scripts to the remote DUT to execute the + required automation test. + + Args: + test_path: Absolute path for the automation file. + remote_directory: Name of the directory where the automation file is + stored in the remote machine. + sftp: SFTP connection to copy the files to the remote machine. + """ + cros_ca = extract_path_prefix(test_path, "cros_ca") + sftp.put_r(cros_ca, remote_directory) + + +def copy_test_files(args): + """Open SFTP connection with the DUT to push the files that will be used + to run the automation code of the required use case. + + Args: + args: Command line arguments passed by the user. + + Returns: + remote_automation_directory: Name of the directory where the + automation file is stored in the remote machine. + """ + cnopts = pysftp.CnOpts() + cnopts.hostkeys = None + try: + with pysftp.Connection( + host=args.target_ip, + port=22, + username=args.username, + private_key=PRIVATE_KEY_PATH, + private_key_pass=None, + cnopts=cnopts, + ) as sftp: + remote_automation_directory = "MyFiles/Automation Code" + if not sftp.exists(remote_automation_directory): + try: + sftp.mkdir(remote_automation_directory) + except FileNotFoundError: + remote_automation_directory = "Automation Code" + sftp.mkdir(remote_automation_directory) + + logging.info("Pushing test files to the DUT") + push_files( + test_path=args.test_path, + remote_directory=remote_automation_directory, + sftp=sftp, + ) + logging.info("Pushing completed successfully.") + return remote_automation_directory + except Exception as e: + logging.error(f"SFTP transfer failed: {e}") + + +def install_wakeonlan_locally_windows(): + """Helper function to install Wake On Lan command line on a Windows + operating system.""" + user_home = os.path.join("C:\\Users", getpass.getuser()) + url = "https://www.depicus.com/downloads/wolcmd.zip" + response = requests.get(url, timeout=20) + compressed_file_path = "wolcmd.zip" + with open(compressed_file_path, "wb") as f: + f.write(response.content) + + with zipfile.ZipFile(compressed_file_path, "r") as zip_ref: + zip_ref.extractall("wolcmd") + + shutil.move("wolcmd/WolCmd.exe", os.path.join(user_home, "WolCmd.exe")) + logging.info("wolcmd installed successfully for the current user on Windows.") + + if os.path.exists(compressed_file_path): + os.remove(compressed_file_path) + logging.info("Compressed file wolcmd.zip removed.") + + +def install_wakeonlan_locally_linux(): + """Helper function to install Wake On Lan command line on a linux + operating system.""" + url = "https://github.com/jpoliv/wakeonlan/archive/master.tar.gz" + install_dir = os.path.join(os.path.expanduser("~"), ".local", "bin") + os.makedirs(install_dir, exist_ok=True) + try: + with urllib.request.urlopen(url) as response, open( + "wakeonlan.tar.gz", "wb" + ) as out_file: + shutil.copyfileobj(response, out_file) + with tarfile.open("wakeonlan.tar.gz", "r:gz") as tar: + tar.extractall() + extracted_dir = "wakeonlan-master" + os.rename(extracted_dir, "wakeonlan") + shutil.move(os.path.join("wakeonlan", "wakeonlan"), install_dir) + logging.info("wakeonlan installed successfully locally.") + except Exception as e: + logging.error(f"Error installing wakeonlan: {e}") + finally: + os.remove("wakeonlan.tar.gz") + shutil.rmtree("wakeonlan", ignore_errors=True) + os.environ["PATH"] += os.pathsep + install_dir + + +def install_wolcmd(): + """Install Wake On Lan command for both Windows and Linux operating + systems.""" + logging.info("Installing wakeonlan") + if SYSTEM == "windows": + install_wakeonlan_locally_windows() + elif SYSTEM == "linux": + install_wakeonlan_locally_linux() + else: + logging.info("Unsupported operating system.") + + +def uninstall_wolcmd(): + """Uninstall WCL command for both Windows and Linux operating systems.""" + logging.info("Removing wakeonlan.") + if SYSTEM == "windows": + user_home = os.path.join("C:\\Users", getpass.getuser()) + wolcmd_path = os.path.join(user_home, "WolCmd.exe") + if os.path.exists(wolcmd_path): + os.remove(wolcmd_path) + logging.info("wolcmd uninstalled successfully from Windows.") + else: + logging.info("wolcmd is not installed on Windows.") + elif SYSTEM == "linux": + install_dir = os.path.join(os.path.expanduser("~"), ".local", "bin") + # Define the path to the wakeonlan script + wakeonlan_path = os.path.join(install_dir, "wakeonlan") + try: + # Remove the wakeonlan script + os.remove(wakeonlan_path) + logging.info("wakeonlan uninstalled successfully.") + except FileNotFoundError: + logging.error("wakeonlan is not installed.") + else: + logging.info("Unsupported operating system.") + + +def establish_ssh_connection(hostname, username, passphrase=None): + """Establishes an SSH connection to a remote server. + + Args: + hostname: The hostname or IP address of the remote server. + username: The username for authentication. + + Returns: + paramiko.SSHClient: An SSHClient object representing the established SSH connection. + """ + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + # Load the private key + private_key = paramiko.RSAKey.from_private_key_file( + PRIVATE_KEY_PATH, password=passphrase + ) + + ssh_client.connect(hostname=hostname, username=username, pkey=private_key) + logging.info("Connected to remote server via SSH successfully!") + return ssh_client + + +def is_ssh_connection_available(hostname, port=22): + """Helper function to check the SSH connection availability every 1 + second. + + Args: + hostname: The hostname or IP address of the remote server. + port: the default port number for the socket. + + Returns: + Boolean: True if the connection is available, False otherwise. + """ + try: + sock = socket.create_connection((hostname, port), timeout=1) + sock.close() + return True + except socket.timeout: + logging.warning(f"Connection to {hostname} timed out") + return False + except ConnectionRefusedError: + logging.warning(f"Connection to {hostname} refused") + return False + except Exception as e: + logging.error(f"An error occurred: {e}") + return False + + +def receive_data_over_ssh(ssh_client): + """Helper function used to get data from the remote client over SFTP + protocol when it's available. + + The function will keep checking if the data available until the timout + period is reached. + + Args: + ssh_client: An SSHClient object representing the SSH connection. + """ + logging.info("Fetching results from the DUT") + sftp = ssh_client.open_sftp() + local_file_path = os.path.join(os.getcwd(), DUT_RESULTS_FILE) + timeout = 400 + start_time = time.time() + + while True: + try: + sftp.get(DUT_RESULTS_FILE, local_file_path) + logging.info("Results received successfully") + print_file_content(DUT_RESULTS_FILE) + time.sleep(10) + sftp.remove(IN_PROGRESS_RESULTS_FILE) + break + except IOError as e: + if time.time() - start_time >= timeout: + logging.error(f"Timeout reached. File not found. {e}") + break + time.sleep(2) + sftp.close() + + +def send_data_over_ssh( + args, + remote_automation_directory, +): + """Send data to a socket on a remote machine over SSH.""" + ssh_client = paramiko.SSHClient() + + try: + ssh_client = establish_ssh_connection( + hostname=args.target_ip, + username=args.username, + ) + ssh_transport = ssh_client.get_transport() + client_agent_port = 8888 + ssh_channel = ssh_transport.open_channel( + "direct-tcpip", + (args.target_ip, client_agent_port), + ("127.0.0.1", client_agent_port), + ) + logging.info( + f"SSH port forwarding established. {remote_automation_directory} " + f"{str(os.path.basename(args.test_path))}" + ) + execute_automation_command = ( + f"--status {args.status} -d '{remote_automation_directory}' -f" + f" {str(os.path.basename(args.test_path))}" + ) + + with ssh_channel as channel: + channel.sendall(execute_automation_command.encode()) + logging.info("Data sent to the socket server on the remote machine.") + + if args.status == "sleep": + logging.info("Checking if the device enter sleep mode") + while is_ssh_connection_available(hostname=args.target_ip, port=22): + pass + install_wolcmd() + time.sleep(2) + if SYSTEM == "windows": + subprocess.run( + [ + "wolcmd", + args.target_mac_address, + args.target_ip, + "255.255.255.0", + "7", + ], + check=True, + ) + elif SYSTEM == "linux": + subprocess.run( + [ + "wakeonlan", + args.target_mac_address, + ], + check=True, + ) + logging.info("Checking SSH connection availability on remote machine.") + start_time = time.time() + while time.time() - start_time < 120: + connection_availability = is_ssh_connection_available( + hostname=args.target_ip, port=22 + ) + if connection_availability: + ssh_client = establish_ssh_connection( + hostname=args.target_ip, + username=args.username, + ) + receive_data_over_ssh(ssh_client) + break + logging.info("Attempting to reconnect...") + else: + logging.error( + "Timeout reached. Remote device may not have woken up " + "from sleep mode" + ) + uninstall_wolcmd() + else: + receive_data_over_ssh(ssh_client) + + except paramiko.AuthenticationException: + logging.error("Authentication failed. Please check your credentials.") + except paramiko.SSHException as ssh_exception: + logging.error(f"Unable to establish SSH connection: {ssh_exception}") + except Exception as e: + logging.error(f"An error occurred: {e}") + finally: + ssh_client.close() + logging.info("Disconnected from remote server.") + + +def read_file(file_name): + """Reads the content of the specified file. + + Args: + file_name: The path to the file to be read. + + Returns: + str: The content of the file as a string. + """ + try: + with open(file_name, "r", encoding="utf-8") as file: + file_content = file.read() + return file_content + except FileNotFoundError as e: + logging.error(f"The specified file does not exist: {e}") + except IOError as e: + logging.error(f"Error reading file '{file_name}'.\n:{e}") + + +def delete_file(file_name): + """Deletes the specified file if it exists. + + Args: + file_name: The path to the file to be deleted. + """ + if os.path.exists(file_name): + os.remove(file_name) + logging.info(f"File {file_name} deleted successfully") + else: + logging.error(f"Results file '{file_name}' not found") + + +def print_file_content(file_name): + """Prints the content of a given file. + + Args: + file_name: The name of the file to print its content. + """ + try: + with open(file_name, "r", encoding="utf-8") as file: + results_content = file.read() + logging.info("File content of %s:\n%s", file_name, results_content) + except FileNotFoundError: + logging.error("File '%s' not found.", file_name) + + +def move_to_out(automation_path): + """Moves the results file to the out directory. + + Args: + automation_path: The absolute path of the automation test. + """ + automation_parent_dir = "cros_ca" + dir_path = extract_path_prefix(automation_path, automation_parent_dir) + current_time = datetime.now().strftime("%Y%m%d-%H%M%S") + results_dir = os.path.join(dir_path, rf"out/{current_time}") + logging.info("Results directory Created") + if not os.path.exists(results_dir): + os.makedirs(results_dir) + source_file = os.path.abspath(AUTOMATION_RESULTS_FILE) + destination_file = os.path.join(results_dir, AUTOMATION_RESULTS_FILE) + shutil.move(source_file, destination_file) + logging.info("===========================================================") + logging.info(f"Results saved to {results_dir}") + + +def main(): + """The entry point for the server agent. + + This function will perform the following: + 1. Set up the logging and logging file handler. + 2. Accept the arguments form the user and handle them. + 3. Copy the test required files to the DUT. + 4. Send test run command over SSH protocol to the DUT. + 5. Clean up the additional generated files in the server machine. + 6. Move the test results to the out directory. + """ + logging_definition() + file_handler = logs_file_setup() + args = get_user_input() + remote_automation_directory = copy_test_files(args=args) + send_data_over_ssh( + args=args, remote_automation_directory=remote_automation_directory + ) + file_handler.close() + delete_file(DUT_RESULTS_FILE) + move_to_out(args.test_path) + + +if __name__ == "__main__": + main()
diff --git a/contrib/cros_ca/remote_testing/setup_scripts/README.md b/contrib/cros_ca/remote_testing/setup_scripts/README.md new file mode 100644 index 0000000..b699441 --- /dev/null +++ b/contrib/cros_ca/remote_testing/setup_scripts/README.md
@@ -0,0 +1,110 @@ +# Remote Testing Helper Scripts + +This directory contains scripts that will reduce the setup steps in [README.md](..%2FREADME.md) file for the server and +the DUT (Windows and Chrome). The +aim of these steps is to reduce the manual effort and to avoid the mistakes that could be generated due to the manual +effort. + +You need to follow the order in the [README.md](..%2FREADME.md) file. + +## Server Setup + +### Work Flow + +This section will describe the script inside ```server_setup``` directory that will do the following: + +1. Create ```.ssh``` directory if it doesn't exist. +2. Copy the testing SSH keys from ```chromiumos/chromite/ssh_keys``` that is used by tast to ```.ssh``` directory. +3. Set permissions for the private key file. +4. Add the private testing key to SSH agent. + +### Usage + +1. Move the script to ```/home/user_name```. +2. Double check that you have the chromium project in ```/home/user_name``` it will be required to copy the testing keys + from inside it. +3. Open terminal and set executable permissions to the script: + ``` + chmod +x server_setup.sh + ``` +4. From the terminal run the server_setup.sh bash script: + ``` + ./server_setup.sh + ``` + +## Windows DUT Setup + +### Work Flow + +This section will describe the two scripts inside ```win_dut_setup``` directory that is responsible for installing +OpenSSHServer feature and do the required steps to make the SSH use public key instead of the password login, this can +be done as below: + +<b> Note:</b>The below steps should be executed for a user in the Administrators group. + +1. Install OpenSSHServer feature. +2. Change OpenSSHServer settings to be run Automatically and start it. +3. Move the public key to ```C:\ProgramData\ssh``` and rename it to <b>administrators_authorized_keys</b>. +4. Change the permission to the administrators_authorized_keys file, using the following command: + ``` + icacls administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" + ``` +5. Create ```.ssh``` directory if it doesn't exist. +6. Copy ```administrators_authorized_keys``` file to ```username/.ssh```. +7. Update the ```C:\ProgramData\ssh\sshd_config``` file by enabling public key authentication. +8. Restart OpenSSHServer. + + +- The first Step (Install OpenSSHServer feature) will be done by ```win_ssh_server_install.ps1```. +- The other steps will be done by ```win_remote_conn_setup.ps1```. + +### Usage + +1. Move the scripts and the public key to ```C:\Users\username ``` +2. Open powerShell terminal as Admin, you can follow the below: + ``` + win + x -> Terminal (Admin) or Windows PowerSell (Admin). + ``` +3. Enable the terminal to execute powerShell scripts by updating the execution policy, using the below command the + current terminal will have the ability to run powerShell scripts: + ``` + Set-ExecutionPolicy -Scope Process Bypass + ``` +4. Run ```win_ssh_server_install.ps1``` script as below: + ``` + .\win_ssh_server_install.ps1 + ``` +5. Restart the device. +6. Repeat the <b>second and third steps</b> then run ```win_remote_conn_setup.ps1``` as below: + ``` + .\win_remote_conn_setup.ps1 + ``` + +## Chrome DUT Setup + +### Work Flow + +This section will describe the script inside ```chrome_dut_setup``` directory that is responsible for preparing the +chrome DUT to accept ssh connection wing a public key used by the server and the DUT devices. where the script will do +the following: + +1. Create .ssh directory if it doesn't exist. +2. Move the public key to ```.ssh``` directory. +3. Rename the public key to ```authorized_keys```. + +### Usage + +1. Move the public key and the script to ```/home/chronos/user```. +2. Login to chronos as an admin by click on ```ctrl + alt + F2``` and sign in with <b> chronos </b>. +3. Change the permissions of the script: + ``` + chmod +x chrome_remote_conn_setup.sh + ``` +4. Copy the script to <b> /bin </b> directory to have the required access to be run, for this do the following: + ``` + sudo mv chrome_remote_conn_setup.sh ../../../bin + ``` +5. Run the script from ```/bin```: + ``` + sudo ./chrome_remote_conn_setup.sh + ``` \ No newline at end of file
diff --git a/contrib/cros_ca/remote_testing/setup_scripts/chrome_dut_setup/chrome_remote_conn_setup.sh b/contrib/cros_ca/remote_testing/setup_scripts/chrome_dut_setup/chrome_remote_conn_setup.sh new file mode 100644 index 0000000..82d4ba4 --- /dev/null +++ b/contrib/cros_ca/remote_testing/setup_scripts/chrome_dut_setup/chrome_remote_conn_setup.sh
@@ -0,0 +1,11 @@ +#!/bin/bash + +# Step 1: Create .ssh directory if it doesn't exist +if [ ! -d ".ssh" ]; then + mkdir .ssh + echo "Created .ssh directory" +fi + +# Step 2: Move and rename testing_rsa.pub to ~/.ssh/authorized_keys +mv -f testing_rsa.pub ~/.ssh/authorized_keys +echo "Moved and renamed testing_rsa.pub to ~/.ssh/authorized_keys"
diff --git a/contrib/cros_ca/remote_testing/setup_scripts/server_setup/server_setup.sh b/contrib/cros_ca/remote_testing/setup_scripts/server_setup/server_setup.sh new file mode 100644 index 0000000..c58a349 --- /dev/null +++ b/contrib/cros_ca/remote_testing/setup_scripts/server_setup/server_setup.sh
@@ -0,0 +1,19 @@ +#!/bin/bash + +# Step 1: Create .ssh directory if it doesn't exist +if [ ! -d "$HOME/.ssh" ]; then + mkdir "$HOME/.ssh" + echo "Created .ssh directory" +fi + +# Step 2: Copy SSH keys to .ssh directory +cp ~/chromiumos/chromite/ssh_keys/testing_rsa ~/chromiumos/chromite/ssh_keys/testing_rsa.pub "$HOME/.ssh/" +echo "Copied testing_rsa and testing_rsa.pub to ~/.ssh" + +# Step 3: Set permissions for testing_rsa +chmod 600 "$HOME/.ssh/testing_rsa" +echo "Changed permissions for testing_rsa" + +# Step 4: Add testing_rsa to SSH agent +ssh-add "$HOME/.ssh/testing_rsa" +echo "Added testing_rsa to SSH agent"
diff --git a/contrib/cros_ca/remote_testing/setup_scripts/win_dut_setup/win_remote_conn_setup.ps1 b/contrib/cros_ca/remote_testing/setup_scripts/win_dut_setup/win_remote_conn_setup.ps1 new file mode 100644 index 0000000..cd552cb --- /dev/null +++ b/contrib/cros_ca/remote_testing/setup_scripts/win_dut_setup/win_remote_conn_setup.ps1
@@ -0,0 +1,48 @@ +echo "Set OpenSSH Server service to start automatically" +Set-Service -Name sshd -StartupType Automatic +echo "Start the OpenSSH Server service" +Start-Service -Name sshd + +# Move testing_rsa.pub to .ssh folder and rename it to administrators_authorized_keys +Move-Item -Path "testing_rsa.pub" -Destination "C:\ProgramData\ssh\administrators_authorized_keys" -Force +Write-Output "Moved testing_rsa.pub to C:\ProgramData\ssh folder and renamed it to administrators_authorized_keys" + +# Change administrators_authorized_keys permissions +$command = "icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant `"Administrators:F`" /grant `"SYSTEM:F`"" +Invoke-Expression $command + +# Check if .ssh directory exists, create it if not +if (-not (Test-Path -Path ".ssh" -PathType Container)) { + New-Item -ItemType Directory -Name ".ssh" + Write-Output "Created .ssh directory" +} + +Copy-Item -Path "C:\ProgramData\ssh\administrators_authorized_keys" -Destination ".ssh" -Force +Write-Output "Copy administrators_authorized_keys to .ssh folder" + +echo "Update sshd_config file" +$sshdConfigPath = "C:\ProgramData\ssh\sshd_config" +$sshdConfigContent = Get-Content -Path $sshdConfigPath +$pubkeyAuthenticationUpdated = $false +# Iterate through each line in the content +foreach ($index in 0..($sshdConfigContent.Length - 1)) { + $line = $sshdConfigContent[$index] + if ($line -match "^\s*#?\s*PubkeyAuthentication(?:\s+no|\s+yes|\s)?$") { + $sshdConfigContent[$index] = "PubkeyAuthentication yes" + $pubkeyAuthenticationUpdated = $true + break + } +} + +# Check if the PubkeyAuthentication line was found and updated +if ($pubkeyAuthenticationUpdated) { + # Write the modified content back to the sshd_config file + $sshdConfigContent | Set-Content -Path $sshdConfigPath -Force + + Write-Host "PubkeyAuthentication setting updated successfully." +} else { + Write-Host "PubkeyAuthentication setting not found in the sshd_config file." +} + +echo "Restart the OpenSSH Server service" +Restart-Service -Name sshd
diff --git a/contrib/cros_ca/remote_testing/setup_scripts/win_dut_setup/win_ssh_server_install.ps1 b/contrib/cros_ca/remote_testing/setup_scripts/win_dut_setup/win_ssh_server_install.ps1 new file mode 100644 index 0000000..3ad5097 --- /dev/null +++ b/contrib/cros_ca/remote_testing/setup_scripts/win_dut_setup/win_ssh_server_install.ps1
@@ -0,0 +1,4 @@ +echo "Install OpenSSH Server feature" +Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 + +Write-Output "Please restart Windows to run sshd!"
diff --git a/contrib/cros_ca/requirements.txt b/contrib/cros_ca/requirements.txt new file mode 100644 index 0000000..808ebce --- /dev/null +++ b/contrib/cros_ca/requirements.txt
@@ -0,0 +1,12 @@ +selenium==3.141.0 +Appium-Python-Client==1.3.0; platform_system == "Windows" +msedge-selenium-tools==3.141.4; platform_system == "Windows" +webdriver-manager==4.0.1; platform_system == "Windows" +numpy==1.26.3; platform_system == "Windows" +psutil==5.9.7; platform_system == "Windows" +urllib3==1.26.18 +python-dotenv==1.0.1 +PyAutoGUI==0.9.54; platform_system == "Windows" +pysftp==0.2.9 +requests==2.31.0 +paramiko~=3.4.0 \ No newline at end of file
diff --git a/contrib/cros_ca/resources/SkipScreenTips.png b/contrib/cros_ca/resources/SkipScreenTips.png new file mode 100644 index 0000000..6c5302c --- /dev/null +++ b/contrib/cros_ca/resources/SkipScreenTips.png Binary files differ
diff --git a/contrib/cros_ca/resources/browse_websites_by_url.xml b/contrib/cros_ca/resources/browse_websites_by_url.xml new file mode 100644 index 0000000..6c34fcf --- /dev/null +++ b/contrib/cros_ca/resources/browse_websites_by_url.xml
@@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<data> + <url>https://www.amazon.com</url> + <url>https://www.google.com</url> + <url>https://www.bing.com</url> + <url>https://www.youtube.com</url> +</data> \ No newline at end of file
diff --git a/contrib/cros_ca/resources/native_app_files/Benchmarking.pptx b/contrib/cros_ca/resources/native_app_files/Benchmarking.pptx new file mode 100644 index 0000000..4de701c --- /dev/null +++ b/contrib/cros_ca/resources/native_app_files/Benchmarking.pptx Binary files differ
diff --git a/contrib/cros_ca/resources/native_app_files/CarsExample.xlsx b/contrib/cros_ca/resources/native_app_files/CarsExample.xlsx new file mode 100644 index 0000000..1246823 --- /dev/null +++ b/contrib/cros_ca/resources/native_app_files/CarsExample.xlsx Binary files differ
diff --git a/contrib/cros_ca/resources/native_app_files/HelloWorld.docx b/contrib/cros_ca/resources/native_app_files/HelloWorld.docx new file mode 100644 index 0000000..9062b70 --- /dev/null +++ b/contrib/cros_ca/resources/native_app_files/HelloWorld.docx Binary files differ
diff --git a/contrib/cros_ca/resources/native_app_files/HelloWorld.pdf b/contrib/cros_ca/resources/native_app_files/HelloWorld.pdf new file mode 100644 index 0000000..cbc5eb0 --- /dev/null +++ b/contrib/cros_ca/resources/native_app_files/HelloWorld.pdf Binary files differ
diff --git a/contrib/cros_ca/resources/native_app_files/TestImage.png b/contrib/cros_ca/resources/native_app_files/TestImage.png new file mode 100644 index 0000000..b287232 --- /dev/null +++ b/contrib/cros_ca/resources/native_app_files/TestImage.png Binary files differ
diff --git a/contrib/cros_ca/resources/web_app_files/Benchmarking.pptx b/contrib/cros_ca/resources/web_app_files/Benchmarking.pptx new file mode 100644 index 0000000..4de701c --- /dev/null +++ b/contrib/cros_ca/resources/web_app_files/Benchmarking.pptx Binary files differ
diff --git a/contrib/cros_ca/resources/web_app_files/CarsExample.xlsx b/contrib/cros_ca/resources/web_app_files/CarsExample.xlsx new file mode 100644 index 0000000..1246823 --- /dev/null +++ b/contrib/cros_ca/resources/web_app_files/CarsExample.xlsx Binary files differ
diff --git a/contrib/cros_ca/resources/web_app_files/HelloWorld.docx b/contrib/cros_ca/resources/web_app_files/HelloWorld.docx new file mode 100644 index 0000000..9062b70 --- /dev/null +++ b/contrib/cros_ca/resources/web_app_files/HelloWorld.docx Binary files differ