1. 为什么选择Python+Selenium做自动化测试
十年前我刚入行测试时,手工点击页面按钮到凌晨三点的经历让我至今难忘。现在Python+Selenium的组合已经成为Web自动化测试的事实标准,这背后有几个关键原因:
首先,Python的语法就像说人话一样简单。我带的实习生经常开玩笑说:"这代码读起来像英语小作文"。比如定位元素只需要driver.find_element(By.ID, "username"),比Java的WebDriver driver = new FirefoxDriver();清爽多了。对于测试脚本这种需要频繁修改的场景,Python的动态类型和简洁语法能节省大量时间。
其次,Selenium的跨浏览器支持实在太香。记得2016年某个紧急项目需要兼容IE11,我用Selenium Grid只花了半天就搭建好了测试环境。现在最新版的Selenium 4更是原生支持CDP协议,连Chrome DevTools的功能都能直接调用。
2. 环境搭建避坑指南
2.1 Python安装的玄学问题
新手最容易栽在Python环境上。最近帮同事排查一个诡异问题:同样的脚本在他电脑上报No module named selenium,但pip list明明显示已安装。最终发现他同时安装了Python 3.8和3.9,而PATH指向的是3.8,pip却装到了3.9的site-packages里。
建议用以下命令检查环境一致性:
which python # 查看Python解释器路径 which pip # 查看pip路径 pip list # 查看当前环境安装的包2.2 Selenium的版本矩阵
Selenium 4有个大坑:它要求浏览器驱动必须严格匹配浏览器版本。上周我用webdriver_manager自动下载驱动时,就遇到了Chrome 115版本找不到对应驱动的尴尬情况。
这是目前验证过的稳定组合:
| 组件 | 版本 | 备注 |
|---|---|---|
| Python | 3.8+ | 3.7已停止维护 |
| Selenium | 4.10+ | 旧版API不同 |
| ChromeDriver | 114-116 | 新版可能不兼容 |
| Firefox(geckodriver) | 最新ESR版 | 企业版更稳定 |
3. 元素定位的实战技巧
3.1 比XPath更香的CSS选择器
早期我特别依赖XPath,直到遇到一个动态ID的商城页面,XPath路径长得能绕显示器三圈。后来发现CSS选择器才是真神器:
# 找包含"购物车"文字的span driver.find_element(By.CSS_SELECTOR, "span:contains('购物车')") # 找class为price且父元素是div的span driver.find_element(By.CSS_SELECTOR, "div > span.price")警告:
:contains()不是标准CSS,仅在Selenium中可用。正式环境建议用text()=xxx的XPath
3.2 等待的艺术
元素加载不同步是最常见的报错原因。我总结出三级等待策略:
- 硬等待:
time.sleep(3)(最后手段) - 隐式等待:
driver.implicitly_wait(10)(全局设置) - 显式等待(推荐方案):
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "dynamicElement")) )4. 框架设计进阶
4.1 Page Object模式改造
看过太多"面条代码"式的测试脚本,维护起来简直噩梦。这是我团队现在用的PO模式结构:
project/ ├── pages/ │ ├── login_page.py │ └── dashboard_page.py ├── tests/ │ └── test_login.py └── utilities/ ├── config_reader.py └── custom_waits.py其中login_page.py的典型写法:
class LoginPage: def __init__(self, driver): self.driver = driver self.username_field = (By.ID, "username") self.password_field = (By.XPATH, "//input[@type='password']") def enter_credentials(self, username, password): self.driver.find_element(*self.username_field).send_keys(username) self.driver.find_element(*self.password_field).send_keys(password) def click_login(self): self.driver.find_element(By.CSS_SELECTOR, "button.login-btn").click()4.2 异常处理模板
这个try-catch模板帮我定位过无数诡异问题:
try: element.click() except Exception as e: screenshot_path = f"screenshots/failure_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png" driver.save_screenshot(screenshot_path) page_source_path = f"page_source/failure_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" with open(page_source_path, "w", encoding="utf-8") as f: f.write(driver.page_source) raise AssertionError(f"元素点击失败,截图已保存到{screenshot_path}") from e5. 真实案例:电商下单流程测试
最近用这套方法测试某跨境电商平台,发现三个关键问题:
- 货币切换BUG:
# 测试步骤 home_page.select_currency("EUR") product_page.add_to_cart() assert "€" in cart_page.get_total_price() # 这里报错最终发现是CDN缓存导致货币符号没更新
地址自动填充失效: 通过分析发现是Google Places API的key过期了
移动端支付跳转超时: 用
driver.context切换到WebView后才成功定位到元素
6. 持续集成实战
在Jenkins中这样配置能节省大量时间:
pipeline { agent any environment { PYTHON_PATH = 'C:\\Python38' CHROME_DRIVER = 'drivers/chromedriver.exe' } stages { stage('Test') { steps { bat ''' set PATH=%PYTHON_PATH%;%PYTHON_PATH%\\Scripts;%PATH% python -m pytest tests/ --alluredir=./allure-results ''' } } } post { always { allure includeProperties: false, jdk: '', results: [[path: 'allure-results']] } } }7. 性能优化技巧
- 复用浏览器会话:
options = webdriver.ChromeOptions() options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") driver = webdriver.Chrome(options=options) # 连接已打开的浏览器- 并行测试方案:
from concurrent.futures import ThreadPoolExecutor def run_test(browser): # 测试代码... with ThreadPoolExecutor(max_workers=3) as executor: executor.map(run_test, ["chrome", "firefox", "edge"])- 智能等待改进:
def wait_for_animation(driver, timeout=30): WebDriverWait(driver, timeout).until( lambda d: d.execute_script("return jQuery.active == 0") )8. 移动端测试特别处理
Hybrid应用测试需要特别注意这个流程:
- 先获取当前上下文
contexts = driver.contexts # ["NATIVE_APP", "WEBVIEW_com.example"]- 切换到WebView
driver.switch_to.context(contexts[1])- 现在可以用常规CSS选择器定位元素了
关键点:必须开启App的WebView调试模式,在代码中需要指定
appPackage和appActivity
9. 常见报错解决方案
这是我整理的排错清单:
| 错误信息 | 可能原因 | 解决方案 |
|---|---|---|
NoSuchElementException | 元素未加载/定位器错误 | 1. 增加等待时间 2. 检查iframe 3. 更新定位器 |
ElementClickInterceptedException | 元素被遮挡 | 1. 滚动到元素 2. 使用JavaScript点击 |
StaleElementReferenceException | DOM已更新 | 重新获取元素引用 |
InvalidSelectorException | XPath/CSS语法错误 | 使用浏览器开发者工具验证 |
10. 最新趋势:Selenium 4的相对定位
Selenium 4新增的相对定位API简直不要太方便:
from selenium.webdriver.support.relative_locator import with_tag_name password_field = driver.find_element(By.ID, "password") submit_button = driver.find_element( with_tag_name("button").below(password_field) )支持的方向包括:
above()below()to_left_of()to_right_of()near()(50像素范围内)
11. 可视化报告生成
用Allure生成这样的报告只需三步:
- 安装依赖:
pip install allure-pytest- 添加装饰器:
@allure.title("测试登录功能") @allure.feature("认证模块") def test_login(): ...- 运行测试:
pytest --alluredir=./allure-results allure serve ./allure-results生成的报告包含自动录制的操作步骤截图和DOM快照
12. 无头模式优化
Chrome无头模式的新参数能提升20%执行速度:
options = webdriver.ChromeOptions() options.add_argument("--headless=new") # 新版无头模式 options.add_argument("--disable-gpu") options.add_argument("--window-size=1920,1080") # 避免响应式布局问题13. 元素状态检查大全
这些判断方法能避免很多Flaky测试:
def is_clickable(element): return element.is_displayed() and element.is_enabled() def is_disappeared(driver, locator, timeout=5): try: WebDriverWait(driver, timeout).until_not( EC.presence_of_element_located(locator) ) return True except TimeoutException: return False14. 跨域处理技巧
遇到跨域iframe时可以这样操作:
# 先切换到iframe driver.switch_to.frame("iframe_id") # 操作iframe内的元素 driver.find_element(...).click() # 切回主文档 driver.switch_to.default_content()注意:如果iframe是动态加载的,需要先等待其加载完成
15. 文件上传终极方案
各种文件上传场景的处理方法:
- 普通input上传:
driver.find_element(By.CSS_SELECTOR, "input[type='file']").send_keys("/path/to/file")Flash/ActiveX控件:考虑用AutoIT或PyWinAuto
拖拽上传:用ActionChains模拟
file_input = driver.find_element(...) drop_area = driver.find_element(...) actions = ActionChains(driver) actions.drag_and_drop(file_input, drop_area).perform()16. 验证码处理策略
- 测试环境禁用验证码(推荐)
- 设置万能验证码如"0000"
- 使用OCR服务(准确率约70%)
- 人工干预接口(通过API获取验证码)
17. 测试数据管理
我用Faker库生成逼真的测试数据:
from faker import Faker fake = Faker("zh_CN") test_user = { "name": fake.name(), "email": fake.email(), "address": fake.address(), "phone": fake.phone_number() }18. 性能对比测试
用time模块简单测算操作耗时:
start_time = time.time() driver.find_element(...).click() end_time = time.time() print(f"点击操作耗时:{(end_time - start_time)*1000:.2f}毫秒")更专业的可以用BrowserMob Proxy做网络性能分析
19. 浏览器日志分析
获取控制台日志排查JS错误:
logs = driver.get_log("browser") for log in logs: if log["level"] == "SEVERE": print(f"[JS错误] {log['message']}")20. 自动化测试的未来
最近在试验SeleniumBase框架,它内置了很多实用功能:
from seleniumbase import BaseCase class MyTest(BaseCase): def test_example(self): self.open("https://example.com") self.assert_title("Example Domain") self.assert_element("div h1")这种扩展框架可能会成为新趋势,特别是它自带的自动重试机制和智能等待,能显著降低Flaky测试的出现概率