Python Selenium自动化测试入门:从环境搭建到实战封装
1. 项目概述:为什么是Python + Selenium?
如果你刚接触测试,或者是一名开发想给自己的项目加点自动化保障,听到“自动化测试”这个词,第一反应可能是“听起来很厉害,但会不会很难上手?”。几年前我也是这么想的,直到我遇到了Python + Selenium这个组合。它就像一个为你量身定做的“网页遥控器”,让你能用写代码的方式,去模拟人在浏览器里的所有操作:点击按钮、输入文字、下拉选择、验证结果。而Python,作为一门语法简洁、库生态丰富的语言,极大地降低了编写这些“遥控指令”的门槛。
简单来说,这个教程的核心目标,就是帮你把“自动化测试”从概念落地为可运行、可复用的脚本。我们不会空谈理论,而是从零开始,手把手带你搭建环境、编写第一个脚本、处理常见的网页交互,直到你能独立完成一个简单的登录流程自动化测试。无论你是测试工程师、后端开发,还是对自动化感兴趣的产品经理,这套组合都能让你快速看到成果,建立信心。我见过太多人因为环境配置的“第一步”就放弃了,所以我们会用最直白的方式,跨过这个门槛。
2. 环境搭建与核心组件解析
万事开头难,自动化测试的“难”往往卡在第一步:环境配置。网上教程众多,但版本兼容性问题常常让人头疼。下面我以当前(教程撰写时)最稳定的组合为例,带你一步步走通。
2.1 Python安装与虚拟环境管理
首先,你需要Python。我强烈建议使用Python 3.8或3.9版本,它们在稳定性和库兼容性上表现最好。不要去官网下最新的3.12或3.13,你可能会遇到一些第三方库尚未适配的问题。
安装要点:
- 从Python官网下载安装包时,务必勾选“Add Python to PATH”这个选项。这能让你在命令行(CMD或终端)中直接使用
python和pip命令,避免后续无数麻烦。 - 安装完成后,打开命令行,输入
python --version。如果能看到类似“Python 3.9.13”的版本信息,恭喜你,第一步成功了。
接下来是 虚拟环境 。这是Python开发中的最佳实践,相当于为你的自动化测试项目创建一个独立的、干净的“工作间”。这样,不同项目间的库版本不会互相冲突。使用 venv 模块创建:
# 在你选定的项目目录下,例如 D:\auto_test
python -m venv venv
这行命令会在当前目录创建一个名为 venv 的文件夹。激活它:
- Windows:
venv\Scripts\activate - Mac/Linux:
source venv/bin/activate
激活后,命令行提示符前会出现 (venv) 字样,表示你已进入这个独立环境。之后所有 pip install 操作都只影响这个环境。
2.2 Selenium库与浏览器驱动
Selenium本身是一个庞大的项目,我们通过Python来调用它。在激活的虚拟环境中,安装Python版的Selenium库非常简单:
pip install selenium
这行命令会从PyPI(Python包索引)下载并安装最新稳定版的 selenium 包。
真正的关键点在于浏览器驱动。 Selenium库是“发号施令”的大脑,而浏览器驱动(Driver)则是连接大脑和浏览器(如Chrome)的“神经”。没有正确的驱动,指令无法传达。
- 确定浏览器版本 :打开你的Chrome浏览器,在地址栏输入
chrome://settings/help,查看版本号(例如,115.0.5790.110)。 - 下载对应驱动 :访问ChromeDriver的官方镜像站(如https://chromedriver.storage.googleapis.com/index.html),找到与你的Chrome浏览器 主版本号完全一致 的驱动版本下载。比如Chrome是115.x,就找115.0.xxxx.x的ChromeDriver。
- 放置驱动 :将下载的
chromedriver.exe(Windows)文件,放置在一个 系统PATH环境变量包含的目录 下,这是最推荐的做法。常见的目录如C:\Windows\或C:\Windows\System32\。你也可以放在项目目录下,但在代码中需要指定完整路径。
注意: 驱动与浏览器版本必须匹配,这是新手踩坑最多的点。版本不匹配通常会报错“This version of ChromeDriver only supports Chrome version XX”。另一个常见错误是驱动文件没有可执行权限(Linux/Mac系统),需要通过
chmod +x chromedriver命令赋予权限。
3. 第一个自动化脚本:从打开网页到元素定位
环境就绪,我们来写第一个脚本。这个脚本的目标是:打开百度首页,在搜索框输入“Selenium”,然后点击“百度一下”按钮。
3.1 初始化WebDriver与基础操作
WebDriver是Selenium的核心对象,所有操作都通过它进行。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time
# 1. 创建WebDriver实例,启动Chrome浏览器
driver = webdriver.Chrome() # 如果chromedriver不在PATH,需指定路径:webdriver.Chrome(executable_path=r‘你的路径\chromedriver.exe’)
# 2. 打开目标网址
driver.get("https://www.baidu.com")
# 3. 让浏览器窗口最大化,避免元素被遮挡
driver.maximize_window()
# 4. 等待页面加载(简单粗暴的方式,后续会改进)
time.sleep(2)
# 5. 定位搜索框并输入内容
# 通过检查百度首页搜索框,我们发现其HTML中有一个id=‘kw’的input标签
search_box = driver.find_element(By.ID, ‘kw‘)
search_box.send_keys(“Selenium”) # 模拟键盘输入
# 6. 定位搜索按钮并点击
# 搜索按钮的id是‘su’
search_button = driver.find_element(By.ID, ‘su‘)
search_button.click()
# 7. 等待搜索结果加载
time.sleep(3)
# 8. 在控制台打印当前页面标题
print(“当前页面标题是:”, driver.title)
# 9. 关闭浏览器窗口
driver.quit()
逐行解释:
webdriver.Chrome():这行代码会启动一个全新的、干净的Chrome浏览器实例。你会在桌面上看到它弹出来。driver.get(url):命令浏览器导航到指定URL。find_element(By.ID, ‘kw’):这是 元素定位 ,是自动化测试的基石。我们通过元素的ID属性(‘kw’)来找到页面上的搜索框。By类提供了多种定位方式。send_keys(“Selenium”)和click():这是 元素操作 ,模拟了人的输入和点击行为。time.sleep(3):这是 强制等待 ,让脚本暂停3秒。这是一个 不好的实践 ,但在我们第一个脚本里用于简单演示。在实际项目中,必须用更智能的等待方式替代它。driver.quit():关闭浏览器并释放WebDriver资源。务必在脚本最后调用,否则后台进程可能不会结束。
3.2 元素定位的八种武器
定位不到元素,是自动化脚本失败的首要原因。Selenium提供了8种主要的定位策略,你需要像熟悉自己工具包里的工具一样熟悉它们。
- ID (
By.ID) : 最优先使用。ID在HTML中应该是唯一的,定位最快、最准确。例如find_element(By.ID, “loginBtn”)。 - Name (
By.NAME) : 次选。Name属性也常用于表单元素。例如find_element(By.NAME, “username”)。 - Class Name (
By.CLASS_NAME) : 注意,一个元素可能有多个CSS类,用这个定位时,需要传入 完整 的类名(多个类名用空格隔开时,只能取其中一个,通常不是好选择)。 - Tag Name (
By.TAG_NAME) : 通过标签名定位,如input,div,a。通常一个页面有很多同类标签,所以常与find_elements(复数)结合使用,获取列表后再筛选。 - Link Text (
By.LINK_TEXT) : 专门用于定位超链接 (<a>标签),使用其 完整的、可见的文本 。例如find_element(By.LINK_TEXT, “忘记密码?”)。 - Partial Link Text (
By.PARTIAL_LINK_TEXT) : 链接文本的部分匹配。例如find_element(By.PARTIAL_LINK_TEXT, “忘记”)。 - CSS Selector (
By.CSS_SELECTOR) : 功能强大,推荐掌握 。它使用CSS选择器语法,非常灵活。例如:#kw(ID选择器,等同By.ID)input.s_ipt(标签+类组合)[name=‘wd’](属性选择器)div#u1 a(层级选择器)
- XPath (
By.XPATH) : 功能最强大,也最复杂 。它使用路径表达式在XML/HTML文档中导航。当元素没有ID、Name等简单属性时,XPath是终极武器。例如://input[@id=‘kw’](查找所有id为kw的input标签)//form[@id=‘form’]//input[1](查找id为form的表单下的第一个input)//a[contains(text(), ‘登录’)](查找文本包含“登录”的链接)
实操心得: 浏览器开发者工具(F12)是你的最佳伙伴。在Elements面板,右键点击某个元素,选择“Copy” -> “Copy selector” 或 “Copy XPath”,可以快速获取该元素的CSS Selector或XPath。但自动生成的XPath可能很长且脆弱(一旦页面结构微调就失效),建议学习编写相对简洁、健壮的XPath,例如尽量使用
@id,@name等稳定属性,慎用绝对路径(以/html/body/div[3]/div[2]...开头的)。
4. 核心交互操作与等待机制
只会定位和点击还不够,真实的网页充满动态内容。我们需要更精细的操作和聪明的等待。
4.1 丰富的元素操作API
除了 click() 和 send_keys() ,WebElement对象还支持很多操作:
- 清除内容 :
element.clear()。在输入前先清空输入框是个好习惯。 - 获取属性/文本 :
link = driver.find_element(By.LINK_TEXT, “新闻”) print(link.get_attribute(‘href‘)) # 获取href属性值 print(link.text) # 获取元素可见文本 - 判断元素状态 :
checkbox = driver.find_element(By.ID, ‘agree‘) print(checkbox.is_selected()) # 是否被选中 print(checkbox.is_enabled()) # 是否可用 print(checkbox.is_displayed()) # 是否可见 - 下拉框选择 :需要引入
Select类。from selenium.webdriver.support.ui import Select province_select = Select(driver.find_element(By.ID, ‘province‘)) province_select.select_by_visible_text(“广东省”) # 按文本选 province_select.select_by_value(“44”) # 按value属性选 province_select.select_by_index(1) # 按索引选(从0开始) - 鼠标悬停与右键 :需要引入
ActionChains类进行复杂鼠标操作。from selenium.webdriver.common.action_chains import ActionChains menu = driver.find_element(By.ID, ‘settingsMenu‘) ActionChains(driver).move_to_element(menu).perform() # 鼠标悬停 # 还可以链式调用:.click_and_hold(), .context_click(右键), .double_click()
4.2 智能等待:告别time.sleep
time.sleep(固定秒数) 是脚本的“毒药”。它无论页面是否加载完成都傻等,严重拖慢脚本速度,且不可靠。Selenium提供了两种智能等待:
-
隐式等待 (Implicit Wait) :为
driver对象设置一个全局的超时时间。在查找 任何一个 元素时,如果元素没有立即出现,WebDriver会轮询查找(默认每0.5秒一次)直到超时。超时前找到则继续,超时未找到则抛出NoSuchElementException。driver.implicitly_wait(10) # 单位:秒 # 这行代码之后的所有find_element操作,最多等待10秒注意: 隐式等待只需设置一次,对整个driver生命周期有效。但它不适用于等待元素的 特定状态 (如可点击、元素包含特定文本)。
-
显式等待 (Explicit Wait) : 这是工业级脚本的标配 。它允许你为某个特定的条件设置等待,条件成立则立即继续,超时则抛出异常。功能强大且灵活。
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待“搜索按钮”出现并且可以被点击,最多等10秒 wait = WebDriverWait(driver, 10) button = wait.until(EC.element_to_be_clickable((By.ID, ‘su‘))) button.click() # 等待页面标题包含“Selenium” wait.until(EC.title_contains(“Selenium”)) # 等待某个元素在页面上可见 element = wait.until(EC.visibility_of_element_located((By.CLASS_NAME, ‘result‘)))expected_conditions模块提供了大量预定义条件,如presence_of_element_located(元素存在于DOM)、visibility_of_element_located(元素可见)、text_to_be_present_in_element(元素包含特定文本)等。
避坑指南: 在实际项目中,我通常 只使用显式等待,并彻底避免隐式等待和
time.sleep。因为混合使用隐式和显式等待会导致不可预知的超时时间。显式等待语义清晰,能精确表达“我在等什么”,让代码更健壮。将常用的等待操作封装成函数,能极大提升代码复用性。
5. 实战:封装一个登录测试用例
让我们把前面的知识组合起来,完成一个具有实际意义的测试用例:自动化测试一个假设的论坛登录功能。
假设我们的被测登录页面有:用户名输入框(id=username)、密码输入框(id=password)、登录按钮(class=btn-login)、登录后的用户昵称显示区域(id=userNickname)。
5.1 测试用例设计与Page Object模式雏形
直接在所有脚本里写 find_element 和 click ,代码会很快变得难以维护。这里我们引入 Page Object (PO) 模式 的思想,将页面元素定位和操作封装起来。
我们先创建一个简单的 login_page.py 文件:
# login_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class LoginPage:
# 页面元素定位器 (Locators),集中管理
USERNAME_INPUT = (By.ID, ‘username‘)
PASSWORD_INPUT = (By.ID, ‘password‘)
LOGIN_BUTTON = (By.CLASS_NAME, ‘btn-login‘)
USER_NICKNAME_SPAN = (By.ID, ‘userNickname‘)
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(self.driver, 10)
def enter_username(self, username):
"""输入用户名"""
user_elem = self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT))
user_elem.clear()
user_elem.send_keys(username)
def enter_password(self, password):
"""输入密码"""
pwd_elem = self.wait.until(EC.visibility_of_element_located(self.PASSWORD_INPUT))
pwd_elem.clear()
pwd_elem.send_keys(password)
def click_login(self):
"""点击登录按钮"""
login_elem = self.wait.until(EC.element_to_be_clickable(self.LOGIN_BUTTON))
login_elem.click()
def get_nickname(self):
"""获取登录后显示的昵称,用于断言"""
nickname_elem = self.wait.until(EC.visibility_of_element_located(self.USER_NICKNAME_SPAN))
return nickname_elem.text
5.2 编写测试脚本与断言
然后,在主测试脚本 test_login.py 中,我们使用封装好的页面类:
# test_login.py
from selenium import webdriver
from login_page import LoginPage
import unittest # 这里使用Python自带的unittest框架,它提供了测试用例组织、断言和固件
class TestForumLogin(unittest.TestCase):
def setUp(self):
"""每个测试方法执行前运行,用于初始化"""
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get(“http://your-test-forum-site.com/login”) # 替换为你的测试地址
self.login_page = LoginPage(self.driver)
def tearDown(self):
"""每个测试方法执行后运行,用于清理"""
self.driver.quit()
def test_login_success(self):
"""测试用例:使用正确用户名密码登录成功"""
# 1. 执行操作
self.login_page.enter_username(“test_user”)
self.login_page.enter_password(“secure_password123”)
self.login_page.click_login()
# 2. 验证结果 (断言)
actual_nickname = self.login_page.get_nickname()
# 使用unittest的断言方法
self.assertEqual(actual_nickname, “test_user”, f“登录后昵称显示错误,期望‘test_user’,实际是‘{actual_nickname}‘”)
def test_login_failed_with_wrong_password(self):
"""测试用例:使用错误密码登录失败"""
self.login_page.enter_username(“test_user”)
self.login_page.enter_password(“wrong_password”)
self.login_page.click_login()
# 假设密码错误时,页面会弹出一个错误提示框,其class为‘alert-error’
error_alert = self.login_page.wait.until(EC.visibility_of_element_located((By.CLASS_NAME, ‘alert-error‘)))
self.assertIn(“密码错误”, error_alert.text)
if __name__ == ‘__main__‘:
unittest.main()
这个脚本展示了自动化测试的核心流程: 准备(Setup) -> 执行(Action) -> 验证(Assert) -> 清理(Teardown) 。使用 unittest 框架能让测试结构更清晰,并生成规范的测试报告。
6. 常见问题排查与高级技巧
即使按照教程一步步来,你也一定会遇到各种报错。下面是我总结的“排错清单”和一些提升脚本质量的高级技巧。
6.1 高频错误与解决方案速查表
| 错误现象/信息 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
selenium.common.exceptions.NoSuchElementException: Message: no such element... |
1. 元素定位表达式写错。 2. 页面尚未加载完成,元素还不存在。 3. 元素在iframe/frame内。 4. 元素是动态生成的(AJAX)。 |
1. 用浏览器开发者工具复核定位器。 2. 添加显式等待 ,等待元素出现/可见。 3. 使用 driver.switch_to.frame(frame_reference) 切换到对应iframe。 4. 等待动态加载完成,通常需要等待某个标志性元素出现。 |
selenium.common.exceptions.ElementNotInteractableException |
元素存在但不可交互(如被遮挡、未可见、禁用状态)。 | 1. 检查元素是否被其他层(如弹窗、遮罩)遮挡。 2. 使用 EC.element_to_be_clickable 等待。 3. 尝试用 ActionChains 或 JavaScript 直接操作。 |
chromedriver‘ cannot be opened because the developer cannot be verified. (Mac) |
Mac系统安全限制。 | 系统偏好设置 -> 安全性与隐私 -> 通用,点击“仍要打开”。或通过终端执行: xattr -d com.apple.quarantine /path/to/chromedriver |
This version of ChromeDriver only supports Chrome version X |
浏览器与驱动版本不匹配。 | 严格按本文2.2节步骤,核对并下载对应版本驱动。 |
| 脚本执行速度慢 | 大量使用 time.sleep 。 |
用显式等待( WebDriverWait )替换所有固定等待。 |
| 在Headless模式下元素找不到 | Headless模式渲染可能与普通模式有细微差别。 | 1. 适当增加等待时间。 2. 在Headless启动选项中添加 --window-size=1920,1080 等参数,确保视口大小。 |
6.2 提升脚本健壮性与效率的技巧
- 使用Page Object Model (POM) :如前所述,将页面元素和操作封装成类。这是中大型自动化项目的基石,能让业务逻辑(测试用例)与页面细节分离,便于维护。
- 数据驱动测试 :将测试数据(如用户名、密码)从脚本中剥离,存放在外部文件(如JSON, CSV, Excel)或数据库中。
unittest的@data装饰器或pytest的@pytest.mark.parametrize可以轻松实现。import pytest @pytest.mark.parametrize(“username, password, expected“, [ (“user1“, “pass1“, True), (“user1“, “wrong“, False), ]) def test_login(self, username, password, expected): # ... 使用参数化的数据执行测试 - Headless模式与无头浏览器 :在服务器或CI/CD管道中运行测试时,不需要看到浏览器界面。使用Headless模式可以节省资源,运行更快。
from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument(“--headless”) # 启用无头模式 chrome_options.add_argument(“--no-sandbox”) # Linux环境常需 chrome_options.add_argument(“--disable-dev-shm-usage”) # 解决共享内存问题 driver = webdriver.Chrome(options=chrome_options) - 处理弹窗、新窗口和Alert :
# 切换到新窗口 main_window = driver.current_window_handle # 点击某个打开新窗口的链接... for handle in driver.window_handles: if handle != main_window: driver.switch_to.window(handle) break # 操作新窗口后,可以切回主窗口 driver.switch_to.window(main_window) # 处理JavaScript Alert/Confirm/Prompt alert = driver.switch_to.alert print(alert.text) # 获取提示文本 alert.accept() # 点击“确定” # alert.dismiss() # 点击“取消” # alert.send_keys(“input text”) # 在Prompt中输入文字 - 执行JavaScript :当Selenium原生API无法完成某些操作时(如滚动到元素、修改元素属性),可以直接注入JS。
# 滚动到页面底部 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) # 滚动到某个元素 element = driver.find_element(By.ID, “someId”) driver.execute_script(“arguments[0].scrollIntoView(true);”, element) # 高亮显示元素(调试用) driver.execute_script(“arguments[0].style.border = ‘3px solid red‘“, element)
走到这里,你已经从一个自动化测试的“门外汉”,变成了一个能够搭建环境、编写基础脚本、定位并操作元素、处理常见交互和等待、甚至初步封装页面对象的“实践者”。Python + Selenium的世界远不止于此,还有测试报告生成(如Allure)、分布式测试(如Selenium Grid)、与CI/CD工具集成等更广阔的空间。但请记住,所有高级应用都建立在扎实的基础之上。我建议你先将本教程中的例子在自己的环境里反复练习,直到你能不参考文档,独立写出一个完整的、带断言的小脚本。遇到报错时,耐心阅读错误信息,对照排查表,善用搜索引擎。自动化测试不是魔法,它是一步一个脚印,用代码将重复劳动解放出来的手艺。
更多推荐
所有评论(0)