1. Selenium浏览器自动化基础认知
做Web自动化测试的朋友对Selenium应该都不陌生,但很多人可能只停留在简单的元素定位和点击操作上。实际上,Selenium提供的浏览器操作API之丰富,足以应对各种复杂的Web交互场景。从最基础的页面导航到复杂的窗口切换,从简单的Cookie处理到全屏截图,这些看似简单的操作在实际项目中往往藏着不少"暗坑"。
我在电商爬虫和Web应用测试中深度使用Selenium近五年,今天就把这些高频使用的浏览器操作整理成实战指南。不同于官方文档的平铺直叙,我会重点分享那些官方没写明但实际项目中必须掌握的技巧,比如:
- 为什么明明调用了maximize_window()却看不到窗口最大化?
- 如何处理那些"顽固"的模态弹窗?
- 在多窗口场景下如何避免常见的句柄丢失问题?
2. 浏览器核心操作实战
2.1 窗口尺寸控制的艺术
新手最常写的代码可能就是driver.maximize_window(),但实际在无头模式(Headless)下这个操作会静默失败。更可靠的做法是显式设置窗口尺寸:
# 推荐方案:兼容有头/无头模式 driver.set_window_size(1920, 1080) # 先设置基准尺寸 try: driver.maximize_window() # 尝试最大化 except WebDriverException: pass # 无头模式忽略错误窗口尺寸对自动化测试影响巨大,特别是响应式页面。我习惯在关键操作前后添加分辨率校验:
def assert_window_size(width, height): current_size = driver.get_window_size() assert current_size['width'] >= width, f"窗口宽度不足,当前{current_size['width']}" assert current_size['height'] >= height, f"窗口高度不足,当前{current_size['height']}"2.2 页面导航的陷阱规避
driver.get(url)看似简单,但在实际项目中会遇到各种问题:
- 页面加载超时(特别是SPA应用)
- 非预期弹窗阻塞
- 证书错误警告
健壮的导航代码应该这样写:
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def safe_get(url, timeout=30): driver.set_page_load_timeout(timeout) try: driver.get(url) # 等待关键元素出现而非单纯sleep WebDriverWait(driver, timeout).until( EC.presence_of_element_located((By.TAG_NAME, 'body')) ) except TimeoutException: # 处理加载超时 driver.execute_script("window.stop();") # 强制停止加载 raise PageLoadTimeout(f"{url} 加载超时")重要提示:对于AJAX密集型页面,建议额外等待特定接口请求完成,可以通过监听浏览器Network日志实现
3. 多窗口与iframe处理
3.1 多窗口切换的可靠方案
当点击某个链接弹出新窗口时,传统做法是遍历window_handles。但在动态页面中,这种方案可能失效。更健壮的做法:
current_handle = driver.current_window_handle # 记录当前句柄 # 点击打开新窗口的操作 element.click() # 等待新窗口出现 WebDriverWait(driver, 10).until( lambda d: len(d.window_handles) > 1 ) # 切换到最新窗口 new_handle = [x for x in driver.window_handles if x != current_handle][0] driver.switch_to.window(new_handle) # 操作完成后切回原窗口 driver.close() driver.switch_to.window(current_handle)3.2 iframe切换的最佳实践
遇到iframe时,很多新手会直接switch_to.frame()而不做任何防护。正确的流程应该是:
def safe_switch_to_frame(identifier): try: if isinstance(identifier, str): # 通过name或id定位 frame = driver.find_element(By.CSS_SELECTOR, f'iframe[name="{identifier}"], iframe[id="{identifier}"]') else: # 直接传入WebElement frame = identifier driver.switch_to.frame(frame) return True except NoSuchElementException: print(f"未找到iframe: {identifier}") return False except StaleElementReferenceException: print("iframe已失效") return False # 使用示例 if safe_switch_to_frame("login_frame"): # 在iframe内操作 driver.find_element(By.NAME, "username").send_keys("test") # 切回主文档 driver.switch_to.default_content()4. 浏览器高级功能实战
4.1 Cookie管理的隐藏技巧
直接使用driver.get_cookies()获取的cookie可能包含HttpOnly属性,这些cookie无法通过JavaScript读取。特殊场景下需要区分处理:
def get_all_cookies(): """获取所有cookie,包括HttpOnly""" return driver.get_cookies() def get_js_cookies(): """仅获取可通过JavaScript访问的cookie""" return driver.execute_script("return document.cookie") # 设置cookie的最佳实践 def set_cookie(name, value, domain=None, path="/", secure=False, http_only=False): cookie = { 'name': name, 'value': value, 'path': path, 'secure': secure, 'httpOnly': http_only } if domain: cookie['domain'] = domain driver.add_cookie(cookie) # 刷新页面使cookie生效 driver.refresh()4.2 浏览器日志捕获
很多开发者不知道Selenium可以获取浏览器控制台日志,这对调试前端错误非常有用:
def get_browser_logs(log_type='browser'): """获取浏览器日志 log_type: browser|driver|client|server """ return driver.get_log(log_type) # 典型应用:监控前端错误 logs = get_browser_logs() errors = [log for log in logs if log['level'] == 'SEVERE'] if errors: print("发现前端错误:") for error in errors: print(f"{error['timestamp']}: {error['message']}")5. 常见问题排查手册
5.1 弹窗处理大全
| 弹窗类型 | 检测方法 | 处理方案 |
|---|---|---|
| JavaScript弹窗 | EC.alert_is_present() | driver.switch_to.alert.accept() |
| 模态对话框 | 查找固定定位的div | 定位关闭按钮后click() |
| 浏览器通知 | 检查权限请求图标 | 启动时添加--disable-notifications |
| 证书错误 | 页面包含"不安全"警告 | 添加--ignore-certificate-errors |
5.2 元素交互异常排查
现象:明明元素存在却点击无效
可能原因1:元素被遮挡
# 检查元素是否可点击 from selenium.webdriver.common.action_chains import ActionChains element = driver.find_element(...) ActionChains(driver).move_to_element(element).perform()可能原因2:需要滚动到视图
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)可能原因3:元素处于禁用状态
assert element.is_enabled(), "元素不可交互"
6. 性能优化实战
6.1 页面加载策略优化
通过设置pageLoadStrategy可以改变页面加载等待策略:
from selenium.webdriver import ChromeOptions options = ChromeOptions() options.page_load_strategy = 'eager' # 可选:none|eager|normal driver = webdriver.Chrome(options=options)- none:不等待页面加载完成
- eager:等待DOMContentLoaded事件
- normal(默认):等待load事件
6.2 网络请求拦截
通过DevTools Protocol可以拦截特定请求:
from selenium.webdriver import Chrome from selenium.webdriver.common.desired_capabilities import DesiredCapabilities caps = DesiredCapabilities.CHROME caps['goog:loggingPrefs'] = {'performance': 'ALL'} driver = Chrome(desired_capabilities=caps) def get_network_requests(): """获取所有网络请求""" logs = driver.get_log('performance') return [log['message'] for log in logs]7. 无头模式特别处理
在Headless模式下,有些行为需要特殊处理:
options = ChromeOptions() options.add_argument('--headless=new') # Chrome 109+推荐语法 # 必须设置的参数 options.add_argument('--disable-gpu') options.add_argument('--window-size=1920,1080') # 解决无头模式下下载文件的问题 options.add_experimental_option("prefs", { "download.default_directory": "/path/to/download", "download.prompt_for_download": False, })无头模式下文件上传的解决方案:
# 传统send_keys()在无头模式可能失效 driver.execute_script(""" const input = document.createElement('input'); input.type = 'file'; input.id = 'hiddenUpload'; input.style.display = 'none'; document.body.appendChild(input); """) file_input = driver.find_element(By.ID, 'hiddenUpload') file_input.send_keys("/path/to/file")