import os import shutil from collections.abc import Generator from pathlib import Path import pytest from selenium import webdriver from selenium.webdriver.chrome.service import Service from .general_fixtures import ( # noqa: F401 # pylint: disable=unused-import nicegui_reset_globals, pytest_addoption, pytest_configure, ) from .screen import Screen # pylint: disable=redefined-outer-name DOWNLOAD_DIR = Path(__file__).parent / 'download' @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): # pylint: disable=unused-argument """Store test outcome in the node for fixture access.""" outcome = yield rep = outcome.get_result() setattr(item, f'rep_{rep.when}', rep) @pytest.fixture def nicegui_chrome_options(chrome_options: webdriver.ChromeOptions) -> webdriver.ChromeOptions: """Configure the Chrome options for the NiceGUI tests.""" chrome_options.add_argument('disable-dev-shm-usage') chrome_options.add_argument('disable-search-engine-choice-screen') chrome_options.add_argument('no-sandbox') chrome_options.add_argument('headless') chrome_options.add_argument('disable-gpu' if 'GITHUB_ACTIONS' in os.environ else '--use-gl=angle') chrome_options.add_argument('window-size=600x600') chrome_options.add_experimental_option('prefs', { 'download.default_directory': str(DOWNLOAD_DIR), 'download.prompt_for_download': False, # To auto download the file 'download.directory_upgrade': True, }) if 'CHROME_BINARY_LOCATION' in os.environ: chrome_options.binary_location = os.environ['CHROME_BINARY_LOCATION'] return chrome_options @pytest.fixture def capabilities(capabilities: dict) -> dict: """Configure the Chrome driver capabilities.""" capabilities['goog:loggingPrefs'] = {'browser': 'ALL'} return capabilities @pytest.fixture(scope='session') def nicegui_remove_all_screenshots() -> None: """Remove all screenshots from the screenshot directory before the test session.""" for name in Screen.SCREENSHOT_DIR.glob('*.png'): name.unlink() @pytest.fixture() def nicegui_driver(nicegui_chrome_options: webdriver.ChromeOptions) -> Generator[webdriver.Chrome, None, None]: """Create a new Chrome driver instance.""" for executable_path in (None, shutil.which('chromedriver'), 'chromedriver'): # Required for ARM devcontainers try: s = Service(executable_path=executable_path) driver_ = webdriver.Chrome(service=s, options=nicegui_chrome_options) break except Exception: continue else: # no break raise RuntimeError('Could not start Chrome WebDriver.') driver_.implicitly_wait(Screen.IMPLICIT_WAIT) driver_.set_page_load_timeout(4) yield driver_ driver_.quit() @pytest.fixture def screen(nicegui_reset_globals, # noqa: F811, pylint: disable=unused-argument nicegui_remove_all_screenshots, # pylint: disable=unused-argument nicegui_driver: webdriver.Chrome, request: pytest.FixtureRequest, caplog: pytest.LogCaptureFixture, ) -> Generator[Screen, None, None]: """Create a new SeleniumScreen fixture.""" os.environ['NICEGUI_SCREEN_TEST_PORT'] = str(Screen.PORT) screen_ = Screen(nicegui_driver, caplog, request) try: yield screen_ logs = [record for record in screen_.caplog.get_records('call') if record.levelname == 'ERROR'] if screen_.is_open: test_failed = hasattr(request.node, 'rep_call') and request.node.rep_call.failed screen_.shot(request.node.name, failed=test_failed or bool(logs)) if logs: pytest.fail('There were unexpected ERROR logs.', pytrace=False) if screen_.is_open and Screen.CATCH_JS_ERRORS: for js_error in screen_.selenium.get_log('browser'): if str(js_error.get('level', '')).upper() in ('SEVERE', 'ERROR') and \ not any(allowed_error in js_error['message'] for allowed_error in screen_.allowed_js_errors): pytest.fail(f'JavaScript console error:\n{js_error}', pytrace=False) finally: os.environ.pop('NICEGUI_SCREEN_TEST_PORT', None) screen_.stop_server() if DOWNLOAD_DIR.exists(): shutil.rmtree(DOWNLOAD_DIR)