""" WebDriver Manager for Cognitive Prism Automation Tests """ import os from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from selenium.webdriver.firefox.service import Service as FirefoxService from selenium.webdriver.firefox.options import Options as FirefoxOptions from selenium.webdriver.edge.service import Service as EdgeService from selenium.webdriver.edge.options import Options as EdgeOptions from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.firefox import GeckoDriverManager from webdriver_manager.microsoft import EdgeChromiumDriverManager from config.config import ( BROWSER, HEADLESS, IMPLICIT_WAIT, PAGE_LOAD_TIMEOUT, DOWNLOAD_DIR ) class DriverManager: """Manages WebDriver instances""" @staticmethod def get_driver(headless=None): """ Creates and returns a WebDriver instance based on configuration Args: headless: Force headless mode (None = use config, True = headless, False = visible) Returns: WebDriver: Configured WebDriver instance """ if BROWSER == "chrome": return DriverManager._get_chrome_driver(headless=headless) elif BROWSER == "firefox": return DriverManager._get_firefox_driver(headless=headless) elif BROWSER == "edge": return DriverManager._get_edge_driver(headless=headless) else: raise ValueError(f"Unsupported browser: {BROWSER}") @staticmethod def _get_chrome_driver(headless=None): """ Creates and returns Chrome WebDriver Args: headless: Force headless mode (None = use config, True = headless, False = visible) """ options = Options() # Determine headless mode use_headless = headless if headless is not None else HEADLESS if use_headless: options.add_argument("--headless") options.add_argument("--disable-gpu") # Performance options (important for load testing) options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") options.add_argument("--window-size=1920,1080") # Additional performance optimizations for load testing if use_headless: options.add_argument("--disable-extensions") options.add_argument("--disable-plugins") # Note: --disable-images might break some tests, so we'll skip it for now # Download preferences prefs = { "download.default_directory": DOWNLOAD_DIR, "download.prompt_for_download": False, "download.directory_upgrade": True, "safebrowsing.enabled": True } options.add_experimental_option("prefs", prefs) # Get ChromeDriver path - webdriver-manager may return directory or file path driver_path = ChromeDriverManager().install() original_path = driver_path # Handle case where webdriver-manager returns a file path instead of directory if os.path.isfile(driver_path): # If it's a file, check if it's the actual executable file_size = os.path.getsize(driver_path) if file_size > 10000000: # > 10MB is definitely the executable # This is the actual chromedriver executable pass # Use it as-is else: # It's not the executable, search in parent directory driver_path = os.path.dirname(driver_path) original_path = driver_path # Ensure we have the actual chromedriver executable, not a directory or text file if os.path.isdir(driver_path): # Strategy: Search for files named exactly "chromedriver" (no extension, no prefix) # and pick the largest one (executable is 10-20MB, text files are < 1MB) candidate_files = [] # Search directory tree for files named exactly "chromedriver" for root, dirs, files in os.walk(original_path): for file in files: # CRITICAL: File must be named EXACTLY "chromedriver" with no extension # This excludes "THIRD_PARTY_NOTICES.chromedriver", "LICENSE.chromedriver", etc. # Use strict equality check - file name must be exactly "chromedriver" if file == "chromedriver" and len(file) == 12: # "chromedriver" is 12 chars full_path = os.path.join(root, file) if os.path.isfile(full_path): file_size = os.path.getsize(full_path) # Only consider files > 1MB (executable size threshold) if file_size > 1000000: candidate_files.append((full_path, file_size)) # If we found candidate files, pick the largest one if candidate_files: # Sort by size (largest first) candidate_files.sort(key=lambda x: x[1], reverse=True) # Pick the largest file - this should be the actual executable (10-20MB) driver_path = candidate_files[0][0] file_size = candidate_files[0][1] # Validate it's large enough (should be > 10MB for ChromeDriver) if file_size < 10000000: # Less than 10MB is suspicious # But allow if it's at least > 1MB and we have no better option if file_size < 1000000: raise FileNotFoundError( f"Found chromedriver file but size ({file_size} bytes) is too small.\n" f"Expected > 1MB. Path: {driver_path}\n" f"Found {len(candidate_files)} candidate file(s)." ) else: # No candidate files found raise FileNotFoundError( f"ChromeDriver executable not found in {original_path}.\n" f"Searched for files named exactly 'chromedriver' with size > 1MB." ) # Final validation - ensure it's a file if not os.path.isfile(driver_path): raise FileNotFoundError( f"ChromeDriver executable not found. Searched in: {original_path}\n" f"Please ensure ChromeDriver is properly installed." ) # Verify it's the actual executable (large file, not text file) file_size = os.path.getsize(driver_path) if file_size < 1000000: # Less than 1MB is likely not the executable raise FileNotFoundError( f"Found chromedriver file but it appears to be a text file (size: {file_size} bytes).\n" f"Expected executable size > 1MB. Path: {driver_path}\n" f"Please check the ChromeDriver installation." ) # Make it executable if needed if not os.access(driver_path, os.X_OK): os.chmod(driver_path, 0o755) if not os.access(driver_path, os.X_OK): raise PermissionError( f"ChromeDriver at {driver_path} is not executable and could not be made executable." ) service = Service(driver_path) driver = webdriver.Chrome(service=service, options=options) # Set timeouts driver.implicitly_wait(IMPLICIT_WAIT) driver.set_page_load_timeout(PAGE_LOAD_TIMEOUT) return driver @staticmethod def _get_firefox_driver(headless=None): """ Creates and returns Firefox WebDriver Args: headless: Force headless mode (None = use config, True = headless, False = visible) """ options = FirefoxOptions() # Determine headless mode use_headless = headless if headless is not None else HEADLESS if use_headless: options.add_argument("--headless") # Download preferences options.set_preference("browser.download.folderList", 2) options.set_preference("browser.download.dir", DOWNLOAD_DIR) options.set_preference("browser.download.useDownloadDir", True) options.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/json,text/csv") service = FirefoxService(GeckoDriverManager().install()) driver = webdriver.Firefox(service=service, options=options) # Set timeouts driver.implicitly_wait(IMPLICIT_WAIT) driver.set_page_load_timeout(PAGE_LOAD_TIMEOUT) return driver @staticmethod def _get_edge_driver(headless=None): """ Creates and returns Edge WebDriver Args: headless: Force headless mode (None = use config, True = headless, False = visible) """ options = EdgeOptions() # Determine headless mode use_headless = headless if headless is not None else HEADLESS if use_headless: options.add_argument("--headless") options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") options.add_argument("--window-size=1920,1080") # Download preferences prefs = { "download.default_directory": DOWNLOAD_DIR, "download.prompt_for_download": False, "download.directory_upgrade": True } options.add_experimental_option("prefs", prefs) service = EdgeService(EdgeChromiumDriverManager().install()) driver = webdriver.Edge(service=service, options=options) # Set timeouts driver.implicitly_wait(IMPLICIT_WAIT) driver.set_page_load_timeout(PAGE_LOAD_TIMEOUT) return driver @staticmethod def quit_driver(driver): """Quits the WebDriver instance""" if driver: driver.quit()