| # Copyright 2023 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import os |
| import logging |
| import posixpath |
| import time |
| |
| from contextlib import contextmanager |
| from typing import List, Optional |
| |
| import attr |
| |
| from chrome.test.variations.drivers import DriverFactory |
| # This import also adds `devil` and `build/android` to `sys.path`. |
| from chrome.test.variations.test_utils import android |
| from selenium import webdriver |
| |
| from devil.android import device_temp_file |
| from devil.android import device_utils |
| from devil.android.sdk import intent |
| |
| # Wait time after loading a page to allow the scrollbar to disappear before |
| # taking a screenshot. |
| SCREENSHOT_WAIT_TIME_SECONDS = 5 |
| |
| @attr.attrs() |
| class AndroidDriverFactory(DriverFactory): |
| channel: str = attr.attrib() |
| avd_config: Optional[str] = attr.attrib() |
| enabled_emulator_window: bool = attr.attrib() |
| ports: List[int] = attr.attrib() |
| |
| #override |
| def __attrs_post_init__(self): |
| super().__attrs_post_init__() |
| self._instance = android.launch_emulator( |
| avd_config=self.avd_config, |
| emulator_window=self.enabled_emulator_window, |
| ports=self.ports) |
| self._device_temp_dir = device_temp_file.NamedDeviceTemporaryDirectory( |
| self.device.adb) |
| self._install_package() |
| |
| def _install_package(self): |
| self._package_name = android.install_chrome(self.channel, self.device) |
| self.device.ClearApplicationState(self.package_name) |
| logging.info('Installed Chrome (%s)', self.package_name) |
| |
| #override |
| @property |
| def supports_startup_timeout(self) -> bool: |
| # Android doesn't support browser startup timeout. |
| return False |
| |
| @property |
| def device_temp_dir(self) -> device_temp_file.NamedDeviceTemporaryDirectory: |
| return self._device_temp_dir |
| |
| @property |
| def package_name(self) -> str: |
| return self._package_name |
| |
| @property |
| def activity_name(self) -> Optional[str]: |
| return None |
| |
| @property |
| def device(self) -> device_utils.DeviceUtils: |
| return self._instance.device |
| |
| def _push_seed(self, seed_file: str): |
| local_seed_file = posixpath.join( |
| self.device_temp_dir.name, os.path.basename(seed_file)) |
| self.device.adb.Push(seed_file, local_seed_file) |
| |
| uid = self.device.GetUidForPackage(self.package_name) |
| self.device.RunShellCommand( |
| ['chown', uid, local_seed_file], as_root=True) |
| return local_seed_file |
| |
| #override |
| def wait_for_screenshot(self): |
| time.sleep(SCREENSHOT_WAIT_TIME_SECONDS) |
| |
| #override |
| @contextmanager |
| def create_driver( |
| self, |
| seed_file: Optional[str] = None, |
| options: Optional[webdriver.ChromeOptions] = None |
| ) -> webdriver.Remote: |
| options = options or self.default_options |
| options.enable_mobile( |
| android_package=self.package_name, |
| android_activity=self.activity_name, |
| ) |
| # We clean up the application dir and place several files there, so |
| # we need to keep the data when running webdriver. |
| options.mobile_options['androidKeepAppDataDir'] = True |
| |
| if seed_file: |
| installed_seed_path = self._push_seed(seed_file) |
| logging.info('Installed seed at (%s)', installed_seed_path) |
| options.add_argument( |
| f'variations-test-seed-path={installed_seed_path}') |
| options.add_argument(f'--fake-variations-channel={self.channel}') |
| # TODO(http://crbug.com/379869158) -- remove this once the new |
| # seed loading mechanism is fixed. |
| options.add_argument( |
| '--force-fieldtrials=SeedFileTrial/Default') |
| |
| driver = None |
| try: |
| yield (driver := webdriver.Chrome(service=self.get_driver_service(), |
| options=options)) |
| finally: |
| if driver: |
| driver.quit() |
| |
| #override |
| def close(self): |
| self._instance.Stop() |
| |
| |
| @attr.attrs() |
| class WebviewDriverFactory(AndroidDriverFactory): |
| |
| #override |
| @property |
| def package_name(self): |
| return 'org.chromium.webview_shell' |
| |
| #override |
| @property |
| def activity_name(self): |
| return '.WebViewBrowserActivity' |
| |
| #override |
| def _install_package(self): |
| # Clear the system webview shell. |
| self.device.ClearApplicationState(self.package_name) |
| |
| ver = android.install_webview(self.channel, self.device) |
| logging.info('Installed webview (%s)', ver) |
| |
| # Launch shell once to create local state files. |
| self.device.StartActivity( |
| intent.Intent( |
| action='android.intent.action.MAIN', |
| package=self.package_name, |
| activity='.WebViewBrowserActivity'), |
| blocking=True) |
| self.device.ForceStop(self.package_name) |
| |
| #override |
| def _push_seed(self, seed_file: str): |
| # Variation seeds for webview are always being loaded from app_webview. |
| package_dir = self.device.GetApplicationDataDirectory(self.package_name) |
| app_data_dir = posixpath.join(package_dir, 'app_webview') |
| local_seed_file = super()._push_seed(seed_file) |
| |
| seed_path = posixpath.join(app_data_dir, 'variations_seed') |
| seed_new_path = posixpath.join(app_data_dir, 'variations_seed_new') |
| self.device.RunShellCommand( |
| ['cp', local_seed_file, seed_path], check_return=True, as_root=True) |
| self.device.RunShellCommand( |
| ['cp', local_seed_file, seed_new_path], check_return=True, as_root=True) |
| return local_seed_file |