1. 项目概述从零到一的UI自动化初体验最近在项目里接手了一个新模块涉及到大量重复的Web界面操作比如表单提交、数据查询和结果校验。手动点来点去不仅效率低还容易出错特别是需要回归测试的时候简直是一场噩梦。于是我开始琢磨着用自动化来解决这个问题。在对比了几种方案后我选择了Python Selenium这个黄金组合。原因很简单Python语法简洁上手快生态丰富Selenium则是Web UI自动化的“老大哥”支持所有主流浏览器社区成熟遇到问题基本都能找到答案。这篇文章就是我这次从零开始搭建UI自动化脚本的完整记录和心得特别适合那些有Python基础想进入自动化测试或者想用自动化解放双手的开发、测试同学参考。我会带你走一遍环境搭建、脚本编写、元素定位、等待机制这些核心环节并分享我踩过的那些坑和填坑技巧。2. 环境搭建与核心工具选型工欲善其事必先利其器。UI自动化的第一步就是把环境给配好。这个过程看似简单但细节决定成败很多新手都在这里卡住。2.1 Python安装与环境变量配置虽然现在很多教程会推荐Anaconda但对于纯粹的UI自动化任务我建议直接安装官方Python。去Python官网下载最新稳定版比如3.8的安装包。安装时务必勾选“Add Python to PATH”这个选项。这是第一个关键点如果忘记勾选后续在命令行里输入python或pip命令会提示找不到需要手动去系统环境变量里添加Python的安装路径和Scripts路径对新手不太友好。安装完成后打开命令行CMD或PowerShell输入python --version和pip --version如果能正确显示版本号说明安装成功。我个人的习惯是安装完Python后会立刻升级pip到最新版命令是python -m pip install --upgrade pip。这样可以避免一些因pip版本过旧导致的包安装失败问题。注意如果你电脑上之前安装过其他版本的Python可能会出现版本冲突。一个干净的、独立的Python环境是好的开始。可以考虑使用py启动器Windows或者虚拟环境来管理。2.2 Selenium库与浏览器驱动的安装Selenium本身是一个库通过pip安装即可pip install selenium。但光有库还不行Selenium需要通过一个叫做“WebDriver”的桥梁来控制浏览器。这个WebDriver需要和你电脑上安装的浏览器版本严格匹配。以最常用的Chrome浏览器为例首先查看你Chrome的版本。在浏览器地址栏输入chrome://settings/help即可看到。然后打开ChromeDriver的官方下载站点搜索ChromeDriver即可找到。找到与你Chrome版本号匹配的驱动版本进行下载。比如你的Chrome是 115.0.5790.102你就需要下载主版本号为115的ChromeDriver。下载下来是一个可执行文件如chromedriver.exe。处理这个文件有三种常见方式方式一推荐给新手将其放在你的Python脚本所在的同一个目录下。这样在代码中指定驱动路径时直接写./chromedriver.exeWindows或./chromedriverMac/Linux即可。方式二一劳永逸将其放在一个固定的目录例如C:\WebDriver\并将该目录路径添加到系统的PATH环境变量中。这样你在代码中初始化时只需写webdriver.Chrome()Selenium会自动从PATH里找到它。方式三使用工具可以使用webdriver-manager这个第三方库它能自动帮你下载和管理匹配的驱动。安装pip install webdriver-manager然后在代码中引入并使用非常方便强烈推荐在团队协作或持续集成环境中使用。我最初用的是方式二但在后来换电脑、浏览器升级时总需要手动更新驱动有点麻烦。现在我的个人项目和公司项目里基本都转向了方式三webdriver-manager省心省力。2.3 集成开发环境IDE的选择写Python脚本一个好用的IDE能极大提升效率。对于自动化脚本开发我主要推荐两款PyCharm社区版免费JetBrains出品专为Python设计功能强大。它的代码提示、调试功能、对虚拟环境的支持都是一流的。特别是调试UI自动化脚本时你可以设置断点一步步看浏览器是如何被操作的对于排查元素定位失败等问题非常有帮助。VS Code免费微软出品轻量级且高度可定制。通过安装Python扩展和Pylance等插件也能获得接近PyCharm的体验。它对Git的支持更原生界面更现代如果你同时写多种语言VS Code的统一性是个优势。我个人的主力是PyCharm因为它“开箱即用”的程度更高项目管理和运行配置更直观。但两者都是绝佳的选择你可以根据喜好来。3. 第一个自动化脚本打开浏览器与基本操作环境准备好了我们来写第一个脚本。这个脚本的目标很简单打开百度首页在搜索框输入“Selenium”然后点击“百度一下”按钮。3.1 脚本结构与核心对象# 导入必要的库 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time # 1. 创建浏览器驱动对象这里使用webdriver-manager自动管理驱动 from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service # 设置ChromeDriver路径自动下载和管理 service Service(ChromeDriverManager().install()) # 2. 启动浏览器并打开百度 driver webdriver.Chrome(serviceservice) driver.get(https://www.baidu.com) # 3. 定位搜索框并输入关键词 # 通过检查元素发现百度搜索框的id是kw search_box driver.find_element(By.ID, kw) search_box.send_keys(Selenium) # 4. 定位“百度一下”按钮并点击 # 通过检查元素发现按钮的id是su search_button driver.find_element(By.ID, su) search_button.click() # 5. 等待一下观察结果实际项目中会用更智能的等待 time.sleep(3) # 6. 关闭浏览器 driver.quit()逐行解析from selenium import webdriver导入核心的WebDriver类。from selenium.webdriver.common.by import By导入定位元素的方式如By.ID, By.NAME, By.XPATH等这是Selenium 4的推荐写法更清晰。driver webdriver.Chrome(...)这行代码会启动一个全新的、干净的Chrome浏览器实例。你看到的会是一个没有任何插件、缓存的新窗口。driver.get(url)让浏览器导航到指定的网址。driver.find_element(By.ID, kw)这是元素定位是UI自动化的基石。这里我们用元素的ID属性来找到搜索框。find_element返回找到的第一个匹配元素如果没找到会抛出异常。send_keys(“Selenium”)向定位到的输入框模拟键盘输入。click()模拟鼠标点击操作。time.sleep(3)强制等待3秒。这是一个不好的实践但在第一个脚本里用于演示。在实际项目中应该使用“智能等待”。driver.quit()关闭浏览器窗口并结束WebDriver会话。一定要调用这个否则后台的chromedriver进程可能不会退出。3.2 元素定位八种武器详解上面脚本用了By.ID这是最可靠、最快的定位方式。但现实中很多元素没有ID或者ID是动态变化的。Selenium提供了丰富的定位策略定位方式示例 (By.XXX, ‘value’)说明与适用场景IDBy.ID, ‘login-button’首选。通常唯一且稳定定位速度最快。NAMEBy.NAME, ‘username’次选。常用于表单输入框但可能不唯一。CLASS_NAMEBy.CLASS_NAME, ‘btn-primary’通过CSS类名定位。注意类名可能有多个用空格分隔。TAG_NAMEBy.TAG_NAME, ‘input’通过标签名定位如div,a。通常用于找多个同类元素。LINK_TEXTBy.LINK_TEXT, ‘忘记密码’精准匹配超链接的完整可见文本。PARTIAL_LINK_TEXTBy.PARTIAL_LINK_TEXT, ‘忘记’匹配超链接的部分可见文本。CSS_SELECTORBy.CSS_SELECTOR, ‘#container .list li:nth-child(2)’功能强大推荐掌握。语法同前端CSS选择器灵活且效率高。XPATHBy.XPATH, ‘//button[type“submit”]’功能最强大但较复杂。可以遍历整个DOM树能处理几乎所有定位难题。实操心得定位优先级ID Name CSS Selector XPath 其他。尽量使用前端的唯一属性。如何获取元素属性在浏览器中按F12打开开发者工具使用左上角的箭头工具点击页面元素即可在右侧的Elements面板看到其HTML代码和属性id, name, class等。CSS Selector 常用语法#id通过ID定位。.class通过类名定位。tag通过标签名定位。[attribute‘value’]通过属性定位。组合input.form-control[name‘email’]XPath 常用语法//tag从根目录开始查找所有tag元素。//tag[attribute‘value’]查找具有特定属性和值的元素。//tag[text()‘文本内容’]通过文本内容定位需完全匹配。//tag[contains(attribute, ‘value’)]属性包含某值。//tag[contains(text(), ‘部分文本’)]文本包含某内容。当页面有多个相似元素时如表格行、列表项使用find_elements注意复数可以返回一个列表然后通过索引操作例如driver.find_elements(By.CLASS_NAME, ‘product-item’)[0].click()。4. 等待机制告别time.sleep拥抱智能等待time.sleep()是脚本稳定性的“毒药”。它固定等待指定时间不管页面是否加载完成。如果网络慢等得不够操作会失败如果网络快等得太多又浪费执行时间。Selenium提供了两种智能等待方式。4.1 隐式等待 (Implicit Wait)隐式等待是设置一个全局的等待时间在查找任何一个元素时如果元素没有立即出现WebDriver会轮询DOM一段时间你设置的时长直到找到元素或超时。driver webdriver.Chrome(serviceservice) # 设置隐式等待时间为10秒 driver.implicitly_wait(10) driver.get(“https://example.com”) # 在接下来的所有find_element操作中如果元素未立即找到最多会等待10秒 element driver.find_element(By.ID, “dynamic-element”)注意事项隐式等待只需要设置一次对整个WebDriver会话周期有效。但它只对find_element和find_elements方法有效对于页面加载、JavaScript执行完毕等情况无效。4.2 显式等待 (Explicit Wait)显式等待更强大、更精确。它允许你为某个特定的操作设置等待条件直到条件满足才继续执行否则在达到最大等待时间超时时抛出异常。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver.get(“https://example.com”) # 创建一个WebDriverWait对象设置最大等待时间10秒轮询间隔0.5秒默认 wait WebDriverWait(driver, 10) # 等待直到ID为‘myDynamicElement’的元素出现在DOM中并可见 element wait.until(EC.presence_of_element_located((By.ID, “myDynamicElement”))) # 或者等待元素可点击 button wait.until(EC.element_to_be_clickable((By.ID, “submit-btn”))) button.click()expected_conditions(EC) 常用条件presence_of_element_located元素出现在DOM中不一定可见。visibility_of_element_located元素出现在DOM中且可见宽高大于0。element_to_be_clickable元素可见且可点击最常用在按钮上。title_contains页面标题包含特定文字。alert_is_present等待警告框出现。我的等待策略在实际项目中我通常混合使用两种等待并以显式等待为主。全局设置一个较短的隐式等待比如5-10秒作为兜底。这可以处理大部分简单的元素查找。对于关键操作特别是点击按钮后页面跳转、弹窗出现、动态内容加载等场景必须使用显式等待。用EC.element_to_be_clickable等待按钮用EC.visibility_of_element_located等待结果数据出现。彻底弃用time.sleep除非是在调试脚本时临时暂停观察。5. 高级操作与框架化思考掌握了基本操作和等待就可以应对大部分场景了。但要让脚本更健壮、更易维护还需要一些高级技巧和框架思维。5.1 处理常见UI组件下拉选择框 (Select)不要用click去点选项使用Selenium提供的Select类。from selenium.webdriver.support.ui import Select select_element driver.find_element(By.ID, “country”) select Select(select_element) # 通过可见文本选择 select.select_by_visible_text(“中国”) # 通过value属性选择 select.select_by_value(“CN”) # 通过索引选择从0开始 select.select_by_index(1)弹窗/警告框 (Alert)# 触发一个alert driver.find_element(By.ID, “alert-btn”).click() # 切换到alert alert driver.switch_to.alert # 获取alert文本 print(alert.text) # 点击确认 alert.accept() # 或者点击取消 # alert.dismiss()iframe/Frame如果元素位于iframe内部必须先切换到该iframe才能操作。# 通过ID或Name切换 driver.switch_to.frame(“iframe-id”) # 或者通过元素对象切换 iframe driver.find_element(By.TAG_NAME, “iframe”) driver.switch_to.frame(iframe) # 操作iframe内的元素 driver.find_element(By.ID, “inside-element”).click() # 操作完成后切换回主文档 driver.switch_to.default_content()浏览器窗口/标签页切换# 获取当前所有窗口的句柄 all_handles driver.window_handles # 获取当前窗口句柄 current_handle driver.current_window_handle # 点击一个打开新窗口的链接 driver.find_element(By.LINK_TEXT, “新窗口”).click() # 切换到新窗口 new_handle [handle for handle in driver.window_handles if handle ! current_handle][0] driver.switch_to.window(new_handle) # 在新窗口操作... # 操作完后可以切回原窗口 driver.switch_to.window(current_handle)5.2 引入Page Object模式当脚本越来越多直接在所有测试用例里写find_element和click会导致代码极度冗余难以维护。这时就需要Page Object (PO) 模式。它的核心思想是将一个页面的元素定位和操作封装成一个类。一个简单的登录页面PO示例# pages/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: # 页面元素定位器 USERNAME_INPUT (By.ID, “username”) PASSWORD_INPUT (By.ID, “password”) LOGIN_BUTTON (By.ID, “submit”) ERROR_MSG (By.CLASS_NAME, “alert-error”) def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) def enter_username(self, username): # 使用显式等待确保元素可见 element self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT)) element.clear() element.send_keys(username) return self # 支持链式调用 def enter_password(self, password): element self.wait.until(EC.visibility_of_element_located(self.PASSWORD_INPUT)) element.clear() element.send_keys(password) return self def click_login(self): element self.wait.until(EC.element_to_be_clickable(self.LOGIN_BUTTON)) element.click() def get_error_message(self): try: element self.wait.until(EC.visibility_of_element_located(self.ERROR_MSG)) return element.text except: return None # 在测试用例中使用 # test_login.py def test_valid_login(): driver webdriver.Chrome(serviceservice) driver.get(“https://example.com/login”) login_page LoginPage(driver) login_page.enter_username(“myuser”).enter_password(“mypass”).click_login() # ... 后续断言 driver.quit()PO模式的好处代码复用元素定位和基础操作只写一次。易于维护页面结构变了只需修改对应的PO类不用改所有测试用例。可读性强测试用例读起来像自然语言login_page.enter_username(“admin”)。5.3 结合单元测试框架如pytest使用pytest或unittest来组织你的测试用例可以方便地进行用例管理、前置后置操作setup/teardown、断言和生成报告。一个使用pytest的简单例子# conftest.py (可选用于共享fixture) import pytest from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service pytest.fixture(scope“function”) # 每个测试函数执行一次 def driver(): service Service(ChromeDriverManager().install()) _driver webdriver.Chrome(serviceservice) _driver.implicitly_wait(5) yield _driver # 测试函数执行时使用这个driver _driver.quit() # 测试函数执行完后退出 # test_baidu_search.py def test_baidu_search(driver): # 使用fixture driver.get(“https://www.baidu.com”) search_box driver.find_element(By.ID, ‘kw’) search_box.send_keys(“pytest”) search_button driver.find_element(By.ID, ‘su’) search_button.click() # 使用显式等待等待结果出现 WebDriverWait(driver, 10).until( EC.title_contains(“pytest”) ) assert “pytest” in driver.title运行测试只需在命令行执行pytest test_baidu_search.py -v。pytest会自动发现并运行以test_开头的函数并注入driverfixture。6. 常见问题排查与实战技巧即使按照最佳实践来写自动化脚本也难免出错。下面是我在实战中遇到的一些典型问题及解决方法。6.1 元素定位失败这是最常见的问题错误信息通常是NoSuchElementException。排查步骤确认页面已加载完成是不是因为页面加载太慢在find_element前增加显式等待。确认定位器是否正确在浏览器的开发者工具Console里用JavaScript验证你的定位器。对于CSS Selector用document.querySelector(‘你的选择器’)对于XPath用$x(‘你的XPath表达式’)。如果返回null或空数组说明定位器写错了。检查元素是否在iframe内如果是必须先switch_to.frame。检查元素是否被遮挡有时元素被其他层如弹窗、广告遮住即使存在也无法交互。可以尝试用JavaScript直接点击driver.execute_script(“arguments[0].click();”, element)。检查元素属性是否动态变化特别是ID、Class里带有随机数的情况。这时需要改用更稳定的定位方式比如用其他固定属性组合XPath或CSS或者用contains、starts-with等函数。6.2 脚本执行不稳定Flaky Tests有时脚本这次能过下次就失败让人头疼。稳定化策略强化等待这是最主要的原因。将所有关键的、可能引起状态变化的操作点击、输入后都加上显式等待等待下一个状态稳定的元素出现。使用重试机制对于非关键性的偶发失败可以使用重试。pytest有pytest.mark.flaky装饰器或者自己用try...except包裹操作进行有限次重试。避免绝对等待彻底不用time.sleep。优化定位器使用唯一且稳定的属性。优先用ID其次用Name再用CSS Selector。尽量避免使用绝对XPath如/html/body/div[3]/div[2]/form/input[1]因为页面结构一变就失效。清理测试环境确保每次测试从一个干净的状态开始。例如登录测试前先清除Cookies或使用隐身模式。6.3 处理浏览器差异虽然Selenium支持多浏览器但不同浏览器Chrome, Firefox, Edge的WebDriver行为可能有细微差别。建议明确主测浏览器通常选择团队主要使用的浏览器如Chrome作为自动化主浏览器。隔离浏览器配置使用独立的浏览器配置文件或直接使用无头模式headless进行自动化避免与手动操作的浏览器插件、缓存互相干扰。Headless模式运行对于在服务器如CI/CD环境上运行的自动化脚本使用无头模式更高效。from selenium.webdriver.chrome.options import Options options Options() options.add_argument(“--headless”) # 启用无头模式 options.add_argument(“--disable-gpu”) # 禁用GPU加速某些系统需要 options.add_argument(“--window-size1920,1080”) # 设置窗口大小 driver webdriver.Chrome(optionsoptions)6.4 性能与可维护性当用例成百上千时脚本的维护成本会急剧上升。优化建议数据驱动将测试数据用户名、密码、搜索关键词从脚本中分离出来存放在CSV、JSON或Excel文件中。测试脚本只负责读取数据和执行逻辑。配置文件将浏览器类型、等待超时时间、基础URL等配置信息放在配置文件如config.ini或config.yaml中方便不同环境切换。日志记录使用Python的logging模块记录脚本执行的关键步骤和错误信息而不是简单用print。这有助于后期排查问题。截图功能在测试失败时自动截图能直观地看到失败时的页面状态。def take_screenshot(driver, name“screenshot”): timestamp time.strftime(“%Y%m%d_%H%M%S”) filename f”{name}_{timestamp}.png” driver.save_screenshot(filename) print(f”Screenshot saved as {filename}”) # 在try...except块中调用 try: some_operation() except Exception as e: take_screenshot(driver, “operation_failed”) raise e使用更高级的框架当项目规模变大可以考虑基于pytestseleniumallure漂亮报告搭建完整的自动化测试框架并集成到Jenkins/GitLab CI等持续集成工具中。UI自动化入门容易但要想写出稳定、高效、易维护的脚本需要持续地实践和总结。从简单的浏览器操作开始逐步引入智能等待、Page Object模式、单元测试框架最终构建起适合自己项目的自动化体系。这个过程本身就是对Web应用交互逻辑和软件工程思维的很好锻炼。
Python+Selenium UI自动化测试实战:从环境搭建到Page Object模式
发布时间:2026/7/2 23:57:44
1. 项目概述从零到一的UI自动化初体验最近在项目里接手了一个新模块涉及到大量重复的Web界面操作比如表单提交、数据查询和结果校验。手动点来点去不仅效率低还容易出错特别是需要回归测试的时候简直是一场噩梦。于是我开始琢磨着用自动化来解决这个问题。在对比了几种方案后我选择了Python Selenium这个黄金组合。原因很简单Python语法简洁上手快生态丰富Selenium则是Web UI自动化的“老大哥”支持所有主流浏览器社区成熟遇到问题基本都能找到答案。这篇文章就是我这次从零开始搭建UI自动化脚本的完整记录和心得特别适合那些有Python基础想进入自动化测试或者想用自动化解放双手的开发、测试同学参考。我会带你走一遍环境搭建、脚本编写、元素定位、等待机制这些核心环节并分享我踩过的那些坑和填坑技巧。2. 环境搭建与核心工具选型工欲善其事必先利其器。UI自动化的第一步就是把环境给配好。这个过程看似简单但细节决定成败很多新手都在这里卡住。2.1 Python安装与环境变量配置虽然现在很多教程会推荐Anaconda但对于纯粹的UI自动化任务我建议直接安装官方Python。去Python官网下载最新稳定版比如3.8的安装包。安装时务必勾选“Add Python to PATH”这个选项。这是第一个关键点如果忘记勾选后续在命令行里输入python或pip命令会提示找不到需要手动去系统环境变量里添加Python的安装路径和Scripts路径对新手不太友好。安装完成后打开命令行CMD或PowerShell输入python --version和pip --version如果能正确显示版本号说明安装成功。我个人的习惯是安装完Python后会立刻升级pip到最新版命令是python -m pip install --upgrade pip。这样可以避免一些因pip版本过旧导致的包安装失败问题。注意如果你电脑上之前安装过其他版本的Python可能会出现版本冲突。一个干净的、独立的Python环境是好的开始。可以考虑使用py启动器Windows或者虚拟环境来管理。2.2 Selenium库与浏览器驱动的安装Selenium本身是一个库通过pip安装即可pip install selenium。但光有库还不行Selenium需要通过一个叫做“WebDriver”的桥梁来控制浏览器。这个WebDriver需要和你电脑上安装的浏览器版本严格匹配。以最常用的Chrome浏览器为例首先查看你Chrome的版本。在浏览器地址栏输入chrome://settings/help即可看到。然后打开ChromeDriver的官方下载站点搜索ChromeDriver即可找到。找到与你Chrome版本号匹配的驱动版本进行下载。比如你的Chrome是 115.0.5790.102你就需要下载主版本号为115的ChromeDriver。下载下来是一个可执行文件如chromedriver.exe。处理这个文件有三种常见方式方式一推荐给新手将其放在你的Python脚本所在的同一个目录下。这样在代码中指定驱动路径时直接写./chromedriver.exeWindows或./chromedriverMac/Linux即可。方式二一劳永逸将其放在一个固定的目录例如C:\WebDriver\并将该目录路径添加到系统的PATH环境变量中。这样你在代码中初始化时只需写webdriver.Chrome()Selenium会自动从PATH里找到它。方式三使用工具可以使用webdriver-manager这个第三方库它能自动帮你下载和管理匹配的驱动。安装pip install webdriver-manager然后在代码中引入并使用非常方便强烈推荐在团队协作或持续集成环境中使用。我最初用的是方式二但在后来换电脑、浏览器升级时总需要手动更新驱动有点麻烦。现在我的个人项目和公司项目里基本都转向了方式三webdriver-manager省心省力。2.3 集成开发环境IDE的选择写Python脚本一个好用的IDE能极大提升效率。对于自动化脚本开发我主要推荐两款PyCharm社区版免费JetBrains出品专为Python设计功能强大。它的代码提示、调试功能、对虚拟环境的支持都是一流的。特别是调试UI自动化脚本时你可以设置断点一步步看浏览器是如何被操作的对于排查元素定位失败等问题非常有帮助。VS Code免费微软出品轻量级且高度可定制。通过安装Python扩展和Pylance等插件也能获得接近PyCharm的体验。它对Git的支持更原生界面更现代如果你同时写多种语言VS Code的统一性是个优势。我个人的主力是PyCharm因为它“开箱即用”的程度更高项目管理和运行配置更直观。但两者都是绝佳的选择你可以根据喜好来。3. 第一个自动化脚本打开浏览器与基本操作环境准备好了我们来写第一个脚本。这个脚本的目标很简单打开百度首页在搜索框输入“Selenium”然后点击“百度一下”按钮。3.1 脚本结构与核心对象# 导入必要的库 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time # 1. 创建浏览器驱动对象这里使用webdriver-manager自动管理驱动 from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service # 设置ChromeDriver路径自动下载和管理 service Service(ChromeDriverManager().install()) # 2. 启动浏览器并打开百度 driver webdriver.Chrome(serviceservice) driver.get(https://www.baidu.com) # 3. 定位搜索框并输入关键词 # 通过检查元素发现百度搜索框的id是kw search_box driver.find_element(By.ID, kw) search_box.send_keys(Selenium) # 4. 定位“百度一下”按钮并点击 # 通过检查元素发现按钮的id是su search_button driver.find_element(By.ID, su) search_button.click() # 5. 等待一下观察结果实际项目中会用更智能的等待 time.sleep(3) # 6. 关闭浏览器 driver.quit()逐行解析from selenium import webdriver导入核心的WebDriver类。from selenium.webdriver.common.by import By导入定位元素的方式如By.ID, By.NAME, By.XPATH等这是Selenium 4的推荐写法更清晰。driver webdriver.Chrome(...)这行代码会启动一个全新的、干净的Chrome浏览器实例。你看到的会是一个没有任何插件、缓存的新窗口。driver.get(url)让浏览器导航到指定的网址。driver.find_element(By.ID, kw)这是元素定位是UI自动化的基石。这里我们用元素的ID属性来找到搜索框。find_element返回找到的第一个匹配元素如果没找到会抛出异常。send_keys(“Selenium”)向定位到的输入框模拟键盘输入。click()模拟鼠标点击操作。time.sleep(3)强制等待3秒。这是一个不好的实践但在第一个脚本里用于演示。在实际项目中应该使用“智能等待”。driver.quit()关闭浏览器窗口并结束WebDriver会话。一定要调用这个否则后台的chromedriver进程可能不会退出。3.2 元素定位八种武器详解上面脚本用了By.ID这是最可靠、最快的定位方式。但现实中很多元素没有ID或者ID是动态变化的。Selenium提供了丰富的定位策略定位方式示例 (By.XXX, ‘value’)说明与适用场景IDBy.ID, ‘login-button’首选。通常唯一且稳定定位速度最快。NAMEBy.NAME, ‘username’次选。常用于表单输入框但可能不唯一。CLASS_NAMEBy.CLASS_NAME, ‘btn-primary’通过CSS类名定位。注意类名可能有多个用空格分隔。TAG_NAMEBy.TAG_NAME, ‘input’通过标签名定位如div,a。通常用于找多个同类元素。LINK_TEXTBy.LINK_TEXT, ‘忘记密码’精准匹配超链接的完整可见文本。PARTIAL_LINK_TEXTBy.PARTIAL_LINK_TEXT, ‘忘记’匹配超链接的部分可见文本。CSS_SELECTORBy.CSS_SELECTOR, ‘#container .list li:nth-child(2)’功能强大推荐掌握。语法同前端CSS选择器灵活且效率高。XPATHBy.XPATH, ‘//button[type“submit”]’功能最强大但较复杂。可以遍历整个DOM树能处理几乎所有定位难题。实操心得定位优先级ID Name CSS Selector XPath 其他。尽量使用前端的唯一属性。如何获取元素属性在浏览器中按F12打开开发者工具使用左上角的箭头工具点击页面元素即可在右侧的Elements面板看到其HTML代码和属性id, name, class等。CSS Selector 常用语法#id通过ID定位。.class通过类名定位。tag通过标签名定位。[attribute‘value’]通过属性定位。组合input.form-control[name‘email’]XPath 常用语法//tag从根目录开始查找所有tag元素。//tag[attribute‘value’]查找具有特定属性和值的元素。//tag[text()‘文本内容’]通过文本内容定位需完全匹配。//tag[contains(attribute, ‘value’)]属性包含某值。//tag[contains(text(), ‘部分文本’)]文本包含某内容。当页面有多个相似元素时如表格行、列表项使用find_elements注意复数可以返回一个列表然后通过索引操作例如driver.find_elements(By.CLASS_NAME, ‘product-item’)[0].click()。4. 等待机制告别time.sleep拥抱智能等待time.sleep()是脚本稳定性的“毒药”。它固定等待指定时间不管页面是否加载完成。如果网络慢等得不够操作会失败如果网络快等得太多又浪费执行时间。Selenium提供了两种智能等待方式。4.1 隐式等待 (Implicit Wait)隐式等待是设置一个全局的等待时间在查找任何一个元素时如果元素没有立即出现WebDriver会轮询DOM一段时间你设置的时长直到找到元素或超时。driver webdriver.Chrome(serviceservice) # 设置隐式等待时间为10秒 driver.implicitly_wait(10) driver.get(“https://example.com”) # 在接下来的所有find_element操作中如果元素未立即找到最多会等待10秒 element driver.find_element(By.ID, “dynamic-element”)注意事项隐式等待只需要设置一次对整个WebDriver会话周期有效。但它只对find_element和find_elements方法有效对于页面加载、JavaScript执行完毕等情况无效。4.2 显式等待 (Explicit Wait)显式等待更强大、更精确。它允许你为某个特定的操作设置等待条件直到条件满足才继续执行否则在达到最大等待时间超时时抛出异常。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver.get(“https://example.com”) # 创建一个WebDriverWait对象设置最大等待时间10秒轮询间隔0.5秒默认 wait WebDriverWait(driver, 10) # 等待直到ID为‘myDynamicElement’的元素出现在DOM中并可见 element wait.until(EC.presence_of_element_located((By.ID, “myDynamicElement”))) # 或者等待元素可点击 button wait.until(EC.element_to_be_clickable((By.ID, “submit-btn”))) button.click()expected_conditions(EC) 常用条件presence_of_element_located元素出现在DOM中不一定可见。visibility_of_element_located元素出现在DOM中且可见宽高大于0。element_to_be_clickable元素可见且可点击最常用在按钮上。title_contains页面标题包含特定文字。alert_is_present等待警告框出现。我的等待策略在实际项目中我通常混合使用两种等待并以显式等待为主。全局设置一个较短的隐式等待比如5-10秒作为兜底。这可以处理大部分简单的元素查找。对于关键操作特别是点击按钮后页面跳转、弹窗出现、动态内容加载等场景必须使用显式等待。用EC.element_to_be_clickable等待按钮用EC.visibility_of_element_located等待结果数据出现。彻底弃用time.sleep除非是在调试脚本时临时暂停观察。5. 高级操作与框架化思考掌握了基本操作和等待就可以应对大部分场景了。但要让脚本更健壮、更易维护还需要一些高级技巧和框架思维。5.1 处理常见UI组件下拉选择框 (Select)不要用click去点选项使用Selenium提供的Select类。from selenium.webdriver.support.ui import Select select_element driver.find_element(By.ID, “country”) select Select(select_element) # 通过可见文本选择 select.select_by_visible_text(“中国”) # 通过value属性选择 select.select_by_value(“CN”) # 通过索引选择从0开始 select.select_by_index(1)弹窗/警告框 (Alert)# 触发一个alert driver.find_element(By.ID, “alert-btn”).click() # 切换到alert alert driver.switch_to.alert # 获取alert文本 print(alert.text) # 点击确认 alert.accept() # 或者点击取消 # alert.dismiss()iframe/Frame如果元素位于iframe内部必须先切换到该iframe才能操作。# 通过ID或Name切换 driver.switch_to.frame(“iframe-id”) # 或者通过元素对象切换 iframe driver.find_element(By.TAG_NAME, “iframe”) driver.switch_to.frame(iframe) # 操作iframe内的元素 driver.find_element(By.ID, “inside-element”).click() # 操作完成后切换回主文档 driver.switch_to.default_content()浏览器窗口/标签页切换# 获取当前所有窗口的句柄 all_handles driver.window_handles # 获取当前窗口句柄 current_handle driver.current_window_handle # 点击一个打开新窗口的链接 driver.find_element(By.LINK_TEXT, “新窗口”).click() # 切换到新窗口 new_handle [handle for handle in driver.window_handles if handle ! current_handle][0] driver.switch_to.window(new_handle) # 在新窗口操作... # 操作完后可以切回原窗口 driver.switch_to.window(current_handle)5.2 引入Page Object模式当脚本越来越多直接在所有测试用例里写find_element和click会导致代码极度冗余难以维护。这时就需要Page Object (PO) 模式。它的核心思想是将一个页面的元素定位和操作封装成一个类。一个简单的登录页面PO示例# pages/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: # 页面元素定位器 USERNAME_INPUT (By.ID, “username”) PASSWORD_INPUT (By.ID, “password”) LOGIN_BUTTON (By.ID, “submit”) ERROR_MSG (By.CLASS_NAME, “alert-error”) def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) def enter_username(self, username): # 使用显式等待确保元素可见 element self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT)) element.clear() element.send_keys(username) return self # 支持链式调用 def enter_password(self, password): element self.wait.until(EC.visibility_of_element_located(self.PASSWORD_INPUT)) element.clear() element.send_keys(password) return self def click_login(self): element self.wait.until(EC.element_to_be_clickable(self.LOGIN_BUTTON)) element.click() def get_error_message(self): try: element self.wait.until(EC.visibility_of_element_located(self.ERROR_MSG)) return element.text except: return None # 在测试用例中使用 # test_login.py def test_valid_login(): driver webdriver.Chrome(serviceservice) driver.get(“https://example.com/login”) login_page LoginPage(driver) login_page.enter_username(“myuser”).enter_password(“mypass”).click_login() # ... 后续断言 driver.quit()PO模式的好处代码复用元素定位和基础操作只写一次。易于维护页面结构变了只需修改对应的PO类不用改所有测试用例。可读性强测试用例读起来像自然语言login_page.enter_username(“admin”)。5.3 结合单元测试框架如pytest使用pytest或unittest来组织你的测试用例可以方便地进行用例管理、前置后置操作setup/teardown、断言和生成报告。一个使用pytest的简单例子# conftest.py (可选用于共享fixture) import pytest from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service pytest.fixture(scope“function”) # 每个测试函数执行一次 def driver(): service Service(ChromeDriverManager().install()) _driver webdriver.Chrome(serviceservice) _driver.implicitly_wait(5) yield _driver # 测试函数执行时使用这个driver _driver.quit() # 测试函数执行完后退出 # test_baidu_search.py def test_baidu_search(driver): # 使用fixture driver.get(“https://www.baidu.com”) search_box driver.find_element(By.ID, ‘kw’) search_box.send_keys(“pytest”) search_button driver.find_element(By.ID, ‘su’) search_button.click() # 使用显式等待等待结果出现 WebDriverWait(driver, 10).until( EC.title_contains(“pytest”) ) assert “pytest” in driver.title运行测试只需在命令行执行pytest test_baidu_search.py -v。pytest会自动发现并运行以test_开头的函数并注入driverfixture。6. 常见问题排查与实战技巧即使按照最佳实践来写自动化脚本也难免出错。下面是我在实战中遇到的一些典型问题及解决方法。6.1 元素定位失败这是最常见的问题错误信息通常是NoSuchElementException。排查步骤确认页面已加载完成是不是因为页面加载太慢在find_element前增加显式等待。确认定位器是否正确在浏览器的开发者工具Console里用JavaScript验证你的定位器。对于CSS Selector用document.querySelector(‘你的选择器’)对于XPath用$x(‘你的XPath表达式’)。如果返回null或空数组说明定位器写错了。检查元素是否在iframe内如果是必须先switch_to.frame。检查元素是否被遮挡有时元素被其他层如弹窗、广告遮住即使存在也无法交互。可以尝试用JavaScript直接点击driver.execute_script(“arguments[0].click();”, element)。检查元素属性是否动态变化特别是ID、Class里带有随机数的情况。这时需要改用更稳定的定位方式比如用其他固定属性组合XPath或CSS或者用contains、starts-with等函数。6.2 脚本执行不稳定Flaky Tests有时脚本这次能过下次就失败让人头疼。稳定化策略强化等待这是最主要的原因。将所有关键的、可能引起状态变化的操作点击、输入后都加上显式等待等待下一个状态稳定的元素出现。使用重试机制对于非关键性的偶发失败可以使用重试。pytest有pytest.mark.flaky装饰器或者自己用try...except包裹操作进行有限次重试。避免绝对等待彻底不用time.sleep。优化定位器使用唯一且稳定的属性。优先用ID其次用Name再用CSS Selector。尽量避免使用绝对XPath如/html/body/div[3]/div[2]/form/input[1]因为页面结构一变就失效。清理测试环境确保每次测试从一个干净的状态开始。例如登录测试前先清除Cookies或使用隐身模式。6.3 处理浏览器差异虽然Selenium支持多浏览器但不同浏览器Chrome, Firefox, Edge的WebDriver行为可能有细微差别。建议明确主测浏览器通常选择团队主要使用的浏览器如Chrome作为自动化主浏览器。隔离浏览器配置使用独立的浏览器配置文件或直接使用无头模式headless进行自动化避免与手动操作的浏览器插件、缓存互相干扰。Headless模式运行对于在服务器如CI/CD环境上运行的自动化脚本使用无头模式更高效。from selenium.webdriver.chrome.options import Options options Options() options.add_argument(“--headless”) # 启用无头模式 options.add_argument(“--disable-gpu”) # 禁用GPU加速某些系统需要 options.add_argument(“--window-size1920,1080”) # 设置窗口大小 driver webdriver.Chrome(optionsoptions)6.4 性能与可维护性当用例成百上千时脚本的维护成本会急剧上升。优化建议数据驱动将测试数据用户名、密码、搜索关键词从脚本中分离出来存放在CSV、JSON或Excel文件中。测试脚本只负责读取数据和执行逻辑。配置文件将浏览器类型、等待超时时间、基础URL等配置信息放在配置文件如config.ini或config.yaml中方便不同环境切换。日志记录使用Python的logging模块记录脚本执行的关键步骤和错误信息而不是简单用print。这有助于后期排查问题。截图功能在测试失败时自动截图能直观地看到失败时的页面状态。def take_screenshot(driver, name“screenshot”): timestamp time.strftime(“%Y%m%d_%H%M%S”) filename f”{name}_{timestamp}.png” driver.save_screenshot(filename) print(f”Screenshot saved as {filename}”) # 在try...except块中调用 try: some_operation() except Exception as e: take_screenshot(driver, “operation_failed”) raise e使用更高级的框架当项目规模变大可以考虑基于pytestseleniumallure漂亮报告搭建完整的自动化测试框架并集成到Jenkins/GitLab CI等持续集成工具中。UI自动化入门容易但要想写出稳定、高效、易维护的脚本需要持续地实践和总结。从简单的浏览器操作开始逐步引入智能等待、Page Object模式、单元测试框架最终构建起适合自己项目的自动化体系。这个过程本身就是对Web应用交互逻辑和软件工程思维的很好锻炼。