1# Copyright 2023 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import pytest 6 7import logging 8import os 9import test_utils 10import shutil 11 12from contextlib import contextmanager 13from selenium import webdriver 14from selenium.common.exceptions import WebDriverException 15from selenium.webdriver import ChromeOptions 16 17def pytest_addoption(parser: pytest.Parser): 18 # By default, running on the hosted platform. 19 parser.addoption('--target-platform', 20 default=test_utils.get_hosted_platform(), 21 dest='target_platform', 22 choices=['linux', 'win', 'mac', 'android', 'cros', 23 'lacros'], 24 help='If present, run for the target platform, ' 25 'defaults to the host platform.') 26 27 parser.addoption('--channel', 28 default='dev', 29 choices=['dev', 'canary', 'beta', 'stable', 'extended'], 30 help='The channel of Chrome to download.') 31 32 parser.addoption('--chromedriver', 33 help='The path to the existing chromedriver. ' 34 'This will ignore --channel and skip downloading.') 35 36# pylint: disable=redefined-outer-name 37@pytest.fixture(scope="session") 38def chromedriver_path(pytestconfig: pytest.Config) -> str: 39 """Returns a path to the chromedriver.""" 40 if cd_path := pytestconfig.getoption('chromedriver'): 41 cd_path = os.path.abspath(cd_path) 42 if not os.path.isfile(cd_path): 43 raise RuntimeError(f'Given chromedriver doesn\'t exist. ({cd_path})') 44 return cd_path 45 46 platform = pytestconfig.getoption('target_platform') 47 channel = pytestconfig.getoption('channel') 48 49 # https://developer.chrome.com/docs/versionhistory/reference/#platform-identifiers 50 downloaded_dir = None 51 if platform == "linux": 52 ver = test_utils.find_version('linux', channel) 53 downloaded_dir = test_utils.download_chrome_linux(version=str(ver)) 54 elif platform == "mac": 55 ver = test_utils.find_version('mac_arm64', channel) 56 downloaded_dir = test_utils.download_chrome_mac(version=str(ver)) 57 elif platform == "win": 58 ver = test_utils.find_version('win64', channel) 59 downloaded_dir = test_utils.download_chrome_win(version=str(ver)) 60 else: 61 raise RuntimeError(f'Given platform ({platform}) is not supported.') 62 63 return os.path.join(downloaded_dir, 'chromedriver') 64 65@pytest.fixture 66def driver_factory(chromedriver_path: str, 67 tmp_path_factory: pytest.TempPathFactory): 68 """Returns a factory that creates a webdriver.""" 69 @contextmanager 70 def factory(chrome_options = None): 71 # Crashpad is a separate process and its dump locations is set via env 72 # variable. 73 crash_dump_dir = tmp_path_factory.mktemp('crash', True) 74 os.environ['BREAKPAD_DUMP_LOCATION'] = str(crash_dump_dir) 75 76 chrome_options = chrome_options or ChromeOptions() 77 chrome_options.add_argument("--headless") 78 chrome_options.add_experimental_option('excludeSwitches', 79 ['disable-background-networking']) 80 driver = None 81 try: 82 driver = webdriver.Chrome(chromedriver_path, options=chrome_options) 83 yield driver 84 except WebDriverException as e: 85 # Report this to be part of test result. 86 if os.listdir(crash_dump_dir): 87 logging.error('Chrome crashed and exited abnormally.\n%s', e) 88 else: 89 logging.error('Uncaught WebDriver exception thrown.\n%s', e) 90 raise 91 finally: 92 if driver: 93 driver.quit() 94 shutil.rmtree(crash_dump_dir, ignore_errors=True) 95 96 return factory 97