Selenium+Python自动化测试入门:从环境搭建到框架设计与实战

Selenium+Python自动化测试入门:从环境搭建到框架设计与实战
1. 项目概述为什么选择SeleniumPython如果你正在为重复的Web界面点击、表单填写、数据校验而感到疲惫或者你的团队正被频繁的回归测试拖慢了迭代速度那么“自动化测试”这个词对你来说一定不陌生。而在众多自动化测试方案中Selenium Python的组合几乎是所有测试工程师和开发者在入门Web UI自动化时第一个会接触到的黄金搭档。它不是什么高深莫测的黑科技而是一套成熟、稳定、且社区生态极其丰富的工具链能让你用相对简单的代码模拟出真实用户在浏览器里的所有操作。简单来说这个组合能帮你做什么想象一下你需要每天凌晨三点检查公司官网的登录功能是否正常或者需要在每次发布新版本前将核心业务流程如用户注册、下单、支付完整地跑上几十遍。手动操作不仅耗时耗力还容易因疲劳而出错。而用Selenium写一段Python脚本设定好时间它就能像不知疲倦的“数字员工”一样精准地执行这些任务并将结果成功或失败的截图、日志清晰地反馈给你。它的核心价值在于将人力从重复、机械的劳动中解放出来提升测试的覆盖率和可靠性为快速交付保驾护航。那么它适合谁呢首先是测试工程师这是他们的核心技能树之一。其次是开发人员尤其是全栈或后端开发掌握UI自动化能让你在自测、构建CI/CD流水线时更加得心应手。甚至对于产品经理、运营人员如果你有强烈的效率提升意愿和一点点编程好奇心也能通过它来实现一些简单的数据抓取或页面监控。门槛并不像想象中那么高关键在于动手实践。2. 环境搭建与核心工具选型工欲善其事必先利其器。在开始编写第一行自动化脚本之前一个稳定、顺手的环境是成功的基石。这一节我会详细拆解从零开始搭建SeleniumPython自动化测试环境的每一步并解释每个选择背后的原因。2.1 Python环境安装与配置Python是这一切的“大脑”。我强烈建议新手不要使用系统自带的Python而是通过Miniconda或Anaconda来管理环境。为什么因为自动化测试项目可能会依赖不同版本的库conda可以为你创建独立的虚拟环境避免包版本冲突这是后续维护的“生命线”。实操步骤下载安装Miniconda访问Miniconda官网下载对应你操作系统Windows/macOS/Linux的Python 3.9版本安装包。选择3.9是因为它在稳定性和对新库的支持上取得了很好的平衡。安装与验证安装过程记得勾选“Add to PATH”。安装完成后打开终端Windows用CMD或PowerShellmacOS/Linux用Terminal输入python --version和conda --version能正确显示版本号即说明安装成功。创建专属虚拟环境在终端中执行conda create -n selenium_env python3.9。这条命令创建了一个名为“selenium_env”的纯净Python 3.9环境。激活环境conda activate selenium_env。你会注意到命令行提示符前面变成了(selenium_env)这表示你后续的所有操作都局限在这个环境里不会污染全局。注意很多教程会直接让你用pip install但在复杂项目中conda环境管理能帮你省去大量“明明昨天还能运行”的调试时间。这是第一个要养成的专业习惯。2.2 Selenium库与浏览器驱动安装Selenium库是Python用来发送操作指令的“遥控器”而浏览器驱动如ChromeDriver则是连接“遥控器”和真实浏览器如Chrome的“翻译官”。两者缺一不可。核心操作安装Selenium库在已激活的selenium_env环境中运行pip install selenium。这会安装最新稳定版的Selenium库。下载浏览器驱动这是新手最容易踩坑的地方。你必须确保驱动版本与你的浏览器版本匹配。查看Chrome版本打开Chrome浏览器点击右上角三个点 - 帮助 - 关于Google Chrome记下版本号例如115.0.5790.110。下载对应ChromeDriver访问ChromeDriver官方下载站或国内镜像站。找到与你的Chrome浏览器主版本号一致例如115的驱动版本进行下载。如果找不到完全一致的选择最接近的稍低版本通常也可行。放置驱动到可执行路径Windows将下载的chromedriver.exe文件放在Python安装目录下的Scripts文件夹里如果你将Python加入了PATH或者直接放在项目根目录下后续代码中需指定路径。macOS/Linux将下载的chromedriver文件解压后通过终端移动到/usr/local/bin目录sudo mv ~/Downloads/chromedriver /usr/local/bin/。为什么这么麻烦因为Selenium WebDriver协议需要与浏览器内核进行精确通信版本不匹配会导致连接失败报错信息通常是“This version of ChromeDriver only supports Chrome version XX”。养成“先看浏览器版本再下驱动”的习惯能避免80%的启动问题。2.3 IDE的选择VSCode与PyCharm写代码需要一个好用的编辑器。VSCode和PyCharm是两大主流选择。VSCode轻量、免费、插件生态强大。通过安装“Python”和“Pylance”插件就能获得优秀的代码提示、调试和虚拟环境管理功能。它适合喜欢高度自定义、同时进行多种语言开发的开发者。PyCharm Community Edition免费且是专为Python设计的IDE。开箱即用对虚拟环境、代码重构、调试的支持更为深入和智能。如果你是专注于Python的测试或开发PyCharm能提供更流畅的体验。我个人更推荐新手使用PyCharm Community Edition。它的环境配置更直观创建新项目时直接选择之前用conda创建的selenium_env环境作为解释器所有依赖管理都被IDE接管了让你更专注于代码本身。3. Selenium核心API与自动化脚本编写环境就绪现在我们进入核心环节学习如何使用Selenium的“语言”来指挥浏览器。我们将从一个最简单的脚本开始逐步增加复杂度。3.1 第一个脚本打开浏览器与访问网页让我们写一个最基础的“Hello World”脚本目标是打开Chrome浏览器导航到百度首页然后关闭浏览器。from selenium import webdriver from selenium.webdriver.common.by import By import time # 1. 创建WebDriver实例启动Chrome浏览器 driver webdriver.Chrome() # 如果chromedriver不在PATH需指定路径webdriver.Chrome(executable_path‘你的路径’) # 2. 打开目标网址 driver.get(https://www.baidu.com) # 3. 让浏览器窗口等待5秒方便我们观察 time.sleep(5) # 4. 关闭浏览器窗口 driver.quit()代码解析与注意事项webdriver.Chrome()这是初始化Chrome浏览器的关键语句。Selenium会启动一个全新的、干净的浏览器实例通常你会看到一个顶部有“Chrome正受到自动测试软件控制”提示的窗口。driver.get(url)这是导航命令。它会等待页面完全加载完成基于document.readyState然后再执行后续代码。time.sleep(5)这是一个“强制等待”。在实际自动化中应尽量避免因为它会无条件地阻塞脚本。这里仅用于演示观察。我们后面会介绍更智能的“显式等待”。driver.quit()关闭浏览器并释放WebDriver会话资源。务必使用quit()而不是close()close()只关闭当前标签页而quit()会清理整个驱动进程避免内存泄漏。执行这个脚本你应该能看到一个Chrome窗口自动打开跳转到百度停留5秒后关闭。恭喜你的第一个自动化程序运行成功了3.2 元素定位自动化操作的基石要让脚本“点击”或“输入”首先必须告诉它“操作谁”。这就是元素定位。Selenium提供了8种主要的定位方式核心是find_element(By.策略 “定位器”)。定位策略 (By.)示例适用场景与特点IDfind_element(By.ID, “kw”)首选。ID通常唯一定位最快、最稳定。NAMEfind_element(By.NAME, “wd”)次选。常用于表单元素但可能不唯一。CLASS_NAMEfind_element(By.CLASS_NAME, “s_ipt”)需注意class可能有多个值空格分隔。TAG_NAMEfind_element(By.TAG_NAME, “input”)标签名重复度极高通常结合其他方法使用。LINK_TEXTfind_element(By.LINK_TEXT, “新闻”)精准定位完整文本的链接a标签。PARTIAL_LINK_TEXTfind_element(By.PARTIAL_LINK_TEXT, “闻”)定位包含部分文本的链接。CSS_SELECTORfind_element(By.CSS_SELECTOR, “#kw”)功能强大语法灵活性能好。是XPath的轻量替代。XPATHfind_element(By.XPATH, ‘//input[id“kw”]’)功能最强大可遍历DOM树但语法复杂性能稍差。实操示例在百度搜索框输入“Selenium”from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys driver webdriver.Chrome() driver.get(https://www.baidu.com) # 定位搜索框通过ID‘kw’ search_box driver.find_element(By.ID, “kw”) # 在搜索框中输入文本 search_box.send_keys(“Selenium自动化测试”) # 模拟按下回车键进行搜索 search_box.send_keys(Keys.RETURN) # 等待一下查看结果 driver.implicitly_wait(5) # 隐式等待后面会讲 driver.quit()定位心法优先级ID Name CSS Selector XPath。优先使用开发者工具F12检查元素是否有唯一ID。如何获取定位器在浏览器中按F12打开开发者工具使用“元素选择器”箭头图标点击页面元素代码会高亮显示。右键高亮代码 - Copy - Copy selector (CSS) 或 Copy XPath。但不要完全依赖复制的路径它们可能又长又脆弱特别是包含动态索引的XPath。要学会自己编写简洁、稳定的定位器。验证定位器在开发者工具的Console面板中可以用JavaScript验证CSS Selectordocument.querySelector(‘你的选择器’)验证XPath$x(‘你的XPath表达式’)。如果返回元素节点则定位有效。3.3 等待机制让脚本更稳定可靠Web页面加载有快有慢元素出现有时差。不加等待的脚本在元素还没出现时就进行操作必然导致NoSuchElementException错误。Selenium提供了三种等待方式。1. 强制等待 (time.sleep)import time time.sleep(5) # 无条件等待5秒不推荐。无论元素是否已就绪都死等固定时间严重拖慢脚本效率。2. 隐式等待 (implicitly_wait)driver.implicitly_wait(10) # 设置全局等待时间为10秒在driver的整个生命周期内每当查找元素时如果元素没有立即找到WebDriver会轮询DOM默认每0.5秒直到找到该元素或超时。它只对find_element系列方法有效。设置一次全局生效。但它无法处理更复杂的条件比如元素可点击、元素可见。3. 显式等待 (WebDriverWaitexpected_conditions)这是最推荐、最健壮的等待方式。它允许你为某个特定条件设置等待条件满足则立即继续超时则抛出异常。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待最多10秒直到ID为‘su’的搜索按钮可见并可点击 wait WebDriverWait(driver, 10) search_button wait.until(EC.element_to_be_clickable((By.ID, “su”))) search_button.click() # 等待直到页面标题包含“Selenium” wait.until(EC.title_contains(“Selenium”))常用预期条件 (EC)presence_of_element_located: 元素出现在DOM中不一定可见。visibility_of_element_located: 元素可见宽高大于0。element_to_be_clickable: 元素可见且可点击。title_is/title_contains: 判断标题。最佳实践混合使用隐式等待和显式等待。设置一个较短的全局隐式等待如5秒作为兜底然后在关键交互步骤如点击、输入前使用显式等待指定更精确的条件。注意混合使用时超时时间可能会叠加。3.4 常用浏览器操作与高级交互掌握了定位和等待你就可以组合出丰富的交互操作了。基本操作# 点击 element.click() # 输入与清空 element.send_keys(“text”) element.clear() # 清空输入框 # 获取元素属性、文本、状态 value element.get_attribute(“value”) # 获取输入框的值 text element.text # 获取元素可见文本 is_enabled element.is_enabled() # 是否可用 is_displayed element.is_displayed() # 是否显示 is_selected element.is_selected() # 复选框/单选框是否被选中 # 提交表单针对form标签内的元素 element.submit()浏览器操作# 导航 driver.back() # 后退 driver.forward() # 前进 driver.refresh() # 刷新 # 窗口与标签页 driver.maximize_window() # 最大化 driver.get_window_size() # 获取尺寸 driver.set_window_size(1024, 768) # 设置尺寸 original_window driver.current_window_handle # 获取当前窗口句柄 driver.switch_to.window(original_window) # 切换窗口 # 执行JavaScript非常强大 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) # 滚动到底部 driver.execute_script(“arguments[0].click();”, element) # 用JS点击元素可绕过某些前端限制处理弹窗与警告框# 切换到alert alert driver.switch_to.alert print(alert.text) # 获取警告文本 alert.accept() # 点击“确定” # alert.dismiss() # 点击“取消” # alert.send_keys(“input”) # 在提示框输入鼠标与键盘高级动作 (ActionChains)用于模拟复杂的鼠标和键盘操作如悬停、拖放、右键菜单、组合键。from selenium.webdriver.common.action_chains import ActionChains actions ActionChains(driver) menu driver.find_element(By.ID, “menu”) submenu driver.find_element(By.ID, “submenu”) # 鼠标悬停 actions.move_to_element(menu).perform() # 拖放元素 source driver.find_element(By.ID, “draggable”) target driver.find_element(By.ID, “droppable”) actions.drag_and_drop(source, target).perform() # 组合键操作 actions.key_down(Keys.CONTROL).send_keys(‘c’).key_up(Keys.CONTROL).perform() # 模拟CtrlC4. 构建可维护的自动化测试框架当脚本越来越多你会发现自己陷入复制粘贴、修改定位器、到处是sleep的泥潭。这时就需要引入一些框架设计思想让代码变得可维护、可复用、可报告。4.1 Page Object Model (POM) 设计模式POM是UI自动化测试的最佳实践和设计模式。其核心思想是将页面抽象成一个类将页面上的元素定位器和操作这些元素的方法封装在这个类里。测试脚本则通过调用页面对象的方法来执行操作从而将页面细节与测试逻辑分离。未使用POM的代码脆弱且难以维护# test_baidu.py def test_search(): driver.find_element(By.ID, “kw”).send_keys(“hello”) driver.find_element(By.ID, “su”).click() assert “hello” in driver.title使用POM改造后# pages/search_page.py from selenium.webdriver.common.by import By class SearchPage: def __init__(self, driver): self.driver driver self.search_input (By.ID, “kw”) # 将定位器定义为元组 self.search_button (By.ID, “su”) def open(self): self.driver.get(“https://www.baidu.com”) def search_for(self, keyword): self.driver.find_element(*self.search_input).send_keys(keyword) self.driver.find_element(*self.search_button).click() # test_cases/test_search.py from pages.search_page import SearchPage def test_search(driver): page SearchPage(driver) page.open() page.search_for(“hello”) assert “hello” in driver.titlePOM的优势高可维护性当页面元素ID从kw变成searchBox时你只需要修改SearchPage类中的一个地方。高可读性测试用例读起来像自然语言page.search_for(“hello”)业务逻辑清晰。低冗余避免了在多个测试脚本中重复编写相同的定位器和操作代码。4.2 数据驱动测试将测试数据如用户名、密码、搜索关键词从测试脚本中分离出来存储在外部的文件如JSON、YAML、Excel、CSV或数据库中。测试脚本读取这些数据来驱动执行。这样增加新的测试用例只需要添加数据而无需修改代码。使用pytest和pytest.mark.parametrize实现数据驱动import pytest import csv def read_test_data(): data [] with open(‘test_data.csv’, ‘r’, encoding‘utf-8’) as f: reader csv.DictReader(f) for row in reader: data.append((row[‘keyword’], row[‘expected_title’])) return data pytest.mark.parametrize(“keyword, expected_title”, read_test_data()) def test_search_with_data(driver, keyword, expected_title): page SearchPage(driver) page.open() page.search_for(keyword) assert expected_title in driver.titletest_data.csv内容keyword,expected_title Selenium,Selenium Python,Python 自动化测试,百度一下4.3 测试报告与日志自动化测试如果不产生报告就像黑盒操作无法知道运行结果。pytest-html和Allure是生成美观测试报告的主流选择。使用pytest-html生成简单报告安装pip install pytest-html运行测试时添加参数pytest --htmlreport.html --self-contained-html运行后会在当前目录生成一个report.html文件用浏览器打开即可查看通过/失败的测试用例详情、执行时间等。集成日志记录使用Python内置的logging模块在关键步骤如开始测试、执行操作、断言、发生异常记录信息便于调试。import logging logging.basicConfig(levellogging.INFO, format‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’) logger logging.getLogger(__name__) def test_example(driver): logger.info(“开始执行搜索测试...”) try: page SearchPage(driver) page.open() logger.info(“已打开百度首页”) page.search_for(“test”) # ... 断言 logger.info(“测试通过”) except Exception as e: logger.error(f“测试失败错误信息{e}”) raise4.4 使用pytest组织测试用例pytest是Python社区最主流的测试框架比自带的unittest更简洁、功能更强大。基础用法测试文件以test_开头如test_search.py。测试函数以test_开头。使用assert进行断言。强大的Fixture功能Fixture用于提供测试所需的固定环境如初始化driver、关闭driver并支持作用域函数、类、模块、会话。# conftest.py (该文件名称固定pytest会自动发现) import pytest from selenium import webdriver pytest.fixture(scope“function”) # 每个测试函数执行一次 def driver(): # 前置操作初始化 _driver webdriver.Chrome() _driver.implicitly_wait(5) yield _driver # 将driver对象传递给测试用例 # 后置操作清理 _driver.quit() # test_file.py def test_with_fixture(driver): # 测试函数通过参数请求fixture driver.get(“https://www.baidu.com”) assert “百度” in driver.title通过conftest.py集中管理driverfixture所有测试文件都可以直接使用实现了资源的统一管理和复用。5. 实战进阶处理复杂场景与最佳实践掌握了基础框架后我们来看看如何应对真实项目中那些令人头疼的“坑”。5.1 处理iframe、窗口与标签页iframe可以理解为“网页中的网页”。要操作iframe内的元素必须先切换到对应的iframe框架。# 通过ID或Name切换 driver.switch_to.frame(“iframe_id_or_name”) # 通过索引切换从0开始 driver.switch_to.frame(0) # 通过WebElement切换 iframe_element driver.find_element(By.TAG_NAME, “iframe”) driver.switch_to.frame(iframe_element) # 操作完iframe内的元素后切回主文档 driver.switch_to.default_content() # 或者切回上一级父iframe driver.switch_to.parent_frame()多窗口/标签页# 获取当前所有窗口句柄 all_handles driver.window_handles # 获取当前窗口句柄 current_handle driver.current_window_handle # 点击一个会打开新窗口的链接 driver.find_element(By.LINK_TEXT, “新窗口”).click() # 等待新窗口出现 WebDriverWait(driver, 10).until(EC.number_of_windows_to_be(2)) # 切换到新窗口 for handle in driver.window_handles: if handle ! current_handle: driver.switch_to.window(handle) break # 在新窗口操作... # 操作完毕后可以切换回原窗口 driver.switch_to.window(current_handle)5.2 处理动态元素、验证码与滑块动态ID/Class有些前端框架如React, Vue会生成随机的ID。此时应避免使用包含动态部分的定位器转而使用更稳定的属性如>stages: - test ui-automation-test: stage: test image: python:3.9-slim # 使用包含Python的Docker镜像 before_script: - apt-get update apt-get install -y wget unzip chromium chromium-driver # 安装浏览器和驱动 - pip install -r requirements.txt # 安装Python依赖 script: - pytest --htmlreport.html --self-contained-html # 执行测试并生成报告 artifacts: when: always paths: - report.html expire_in: 1 week only: - main # 仅在main分支推送时触发在Jenkins中你可以创建一个“自由风格”或“Pipeline”项目通过shell命令执行类似的步骤并配置插件如HTML Publisher plugin来展示生成的report.html。5.4 反爬虫检测与规避策略越来越多的网站会检测Selenium等自动化工具。特征包括window.navigator.webdriver属性为true、存在特定的CDPChrome DevTools Protocol标志等。应对策略使用undetected-chromedriver这是一个第三方库专门用于修改ChromeDriver特征使其更接近普通浏览器。import undetected_chromedriver as uc driver uc.Chrome()添加实验性选项原生Seleniumfrom selenium.webdriver import ChromeOptions options ChromeOptions() # 添加常用选项使其更像真人 options.add_argument(“--disable-blink-featuresAutomationControlled”) options.add_experimental_option(“excludeSwitches”, [“enable-automation”]) options.add_experimental_option(‘useAutomationExtension’, False) driver webdriver.Chrome(optionsoptions) # 执行CDP命令覆盖navigator.webdriver属性 driver.execute_cdp_cmd(“Page.addScriptToEvaluateOnNewDocument”, { “source”: “”” Object.defineProperty(navigator, ‘webdriver’, { get: () undefined }); “”” })行为模拟在操作中加入随机延迟、模拟人类鼠标移动轨迹而非直接click()避免过快的、程式化的操作模式。重要提醒请务必在合法合规、获得授权的前提下进行自动化操作尊重网站的robots.txt协议和服务条款。6. 常见问题排查与调试技巧即使按照最佳实践编写脚本也难免会遇到各种问题。这里记录了一些高频问题和我的排查心得。6.1 元素定位失败NoSuchElementException这是最常见的问题。排查思路如下检查定位器首先在浏览器开发者工具的Console中用$x()或document.querySelector()验证你的XPath或CSS Selector是否正确、唯一。检查等待元素是否还没加载出来在操作前添加显式等待WebDriverWait.until。检查iframe目标元素是否在iframe内如果是需要先switch_to.frame。检查新窗口操作是否打开了新窗口需要先switch_to.window。检查元素状态元素是否被隐藏display: none或不可交互使用EC.element_to_be_clickable或EC.visibility_of_element_located。检查页面结构是否已变前端代码更新了需要更新你的定位器。6.2 脚本在IDE中运行正常但在CI/命令行中失败无头模式问题CI环境通常是无头模式没有图形界面。有些网页在无头模式下渲染或行为可能不同。在本地用options.add_argument(“--headless”)测试一下。路径问题确保CI环境中chromedriver的路径正确或已加入PATH。依赖缺失CI环境可能缺少某些系统库。确保Docker镜像或构建脚本中安装了所有必要依赖如chromium、libnss3等。资源不足CI环境的CPU/内存可能较低导致页面加载超慢。适当增加隐式/显式等待的超时时间。6.3 如何高效调试活用driver.save_screenshot(‘error.png’)在关键步骤或捕获异常后截图能直观看到失败时的页面状态。try: element.click() except Exception as e: driver.save_screenshot(‘click_failed.png’) raise使用driver.page_source打印或保存失败时的页面HTML源码分析DOM结构。使用pytest的-v详细输出和--pdb失败时进入调试器选项。临时禁用无头模式在调试时注释掉--headless参数亲眼观察脚本的执行过程。在关键步骤后添加time.sleep(2)虽然不推荐用于最终脚本但在调试时短暂的sleep能让你看清执行流程。6.4 性能优化与稳定性提升复用浏览器会话对于需要登录的测试可以复用同一个driver实例跑多个测试用例避免重复登录。使用pytest的scope“session”的fixture。并行执行使用pytest-xdist插件可以并行运行测试用例大幅缩短测试套件总执行时间。优化等待策略用显式等待替代固定等待用EC.invisibility_of_element_located等待加载动画消失用EC.staleness_of等待元素被移除。清理测试数据每个测试用例应该是独立的。使用setup和teardown或fixture确保测试前后状态干净避免用例间相互影响。走到这里你已经从一个自动化测试的“门外汉”变成了一个能够搭建框架、处理复杂场景、并集成到开发流程中的实践者。SeleniumPython的生态非常庞大永远有新的场景和工具值得探索比如移动端自动化Appium、更现代的浏览器自动化工具Playwright、Cypress。但万变不离其宗你在这里学到的关于元素定位、等待、框架设计、问题排查的核心思想是通往更广阔自动化世界的坚实基石。记住自动化测试不是一蹴而就的从一个小脚本开始逐步迭代让它真正为你的项目和团队创造价值。