news 2026/9/8 5:43:29

Selenium Web自动化测试实战:从环境搭建到框架设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Selenium Web自动化测试实战:从环境搭建到框架设计

1. 先说清楚:Selenium到底是测试工具还是爬虫工具

我最早接触Selenium,是因为一个特别常见的误解——以为它是爬虫工具。当时有个需求要抓某个动态渲染的网站,用requests拿不到数据,搜索一圈,所有人都在说“用Selenium”,于是我也跟着用了。用了一段时间之后才明白,这个定位其实不完全对。Selenium的官方身份是Web自动化测试框架,它的本职工作是模拟用户在浏览器里的操作,然后验证页面行为是否符合预期。爬虫只是它的“副业”,或者说,是自动化能力的一个衍生用途。

理解这一点很重要。因为当你把它当“测试工具”来用的时候,你会更关注稳定性、可重复性、元素定位的准确性、等待条件的可靠性;而当你把它当“爬虫工具”来用的时候,你只会关心数据有没有抓到。这两种心态会导致完全不同的用法。比如测试场景里,你必须要写显式等待,必须处理元素找不到的异常,必须考虑页面并发渲染的顺序,但在“能跑就行”的爬虫脚本里,这些基本都是糊弄过去的。这也是很多Selenium初学者跑了两三天就开始踩坑的根本原因——他们没搞清楚Selenium的底层逻辑是“验证”,而不是“抓取”。

再说得直白一点:Selenium是一个WebDriver协议的实现。它启动浏览器、打开页面、执行JavaScript、点击按钮、输入文本、读取页面状态,全部都是通过一套标准化的驱动接口来完成的。你在Python里写driver.find_element(...),本质上是通过HTTP协议告诉浏览器驱动“帮我去页面上找一个元素”,然后浏览器驱动再去调用浏览器内核执行这个操作。这不是什么黑魔法,它就是一套远程控制协议。

所以,想真正掌握Python + Selenium的自动化Web测试,你需要搞清楚的无非是四件事:环境怎么搭、定位怎么写、等待怎么等、异常怎么处理。这四句话听起来简单,但每一项拆开都有不少可讲的细节。这篇文章里,我不打算给你抄一段“官网示例”然后敷衍了事,我会站在实际项目的角度,把Selenium自动化测试里最核心、最容易翻车、也最值得花时间的部分全部罗列出来。

如果你现在的水平是:Python基础语法还不熟,不太清楚怎么安装依赖包,分不清WebDriver和Selenium的关系,或者已经能跑通Demo了但一遇到动态页面、弹窗、iframe、文件下载就开始抓狂——那这篇文章应该能把你的进度往前推一大截。

2. 环境搭建这一步,坑比你想的多得多

2.1 Python环境本身怎么搞才省心

说句实话,Selenium本身装起来不费劲,pip install selenium一行命令就够了。真正让你头疼的,是Python环境、浏览器驱动、IDE配置这三样东西的组合问题。

先聊Python环境。我见过很多初学者卡在“装Python”这一步,其实不是Python难装,是他们不知道该装哪个版本、装完了怎么验证。如果你是Windows系统,去Python官网(python.org)下载安装包的时候,有一个非常关键的选项——安装时务必勾选“Add Python to PATH”。这个选项不勾选,装完了之后在命令行里敲python会提示“不是内部或外部命令”,然后你就开始怀疑人生了。

装完之后,打开命令行(cmd或PowerShell),输入下面的命令验证:

python --version pip --version

能正常输出版本号,恭喜你,环境的第一关过了。

然后就是虚拟环境的问题。我强烈建议你从第一天开始就养成用虚拟环境的习惯。这不是折腾,这是保命。因为真实项目里你不太可能只装一个selenium,你还要装pytest、requests、webdriver-manager、pandas,这些包之间有版本依赖关系。你要是全装到系统Python里,哪天某个包升级把另一个包搞坏了,你都不知道是谁干的。venv就是给你每个项目单独隔离一套环境,互相不干扰。

在项目目录下执行:

python -m venv venv

Windows下激活虚拟环境:

venv\Scripts\activate

Mac/Linux下激活虚拟环境:

source venv/bin/activate

激活之后,命令行前面会出现(venv)的标识,这时候你再安装Selenium,它就会被安装到这个独立的虚拟环境里。

2.2 Selenium和WebDriver的版本匹配

这是让无数人崩溃的一个坑。Selenium这套东西分两部分:一部分是Python库(selenium包),另一部分是浏览器驱动(如ChromeDriver、GeckoDriver、EdgeDriver)。你需要安装跟你浏览器版本精确匹配的驱动,然后通过代码里的webdriver.Chrome()去调用它。

早期的Selenium用法是:

from selenium import webdriver driver = webdriver.Chrome(executable_path='path/to/chromedriver')

如果你用的是Selenium 4.x(现在是主流版本),写法稍微变了一点:

from selenium import webdriver from selenium.webdriver.chrome.service import Service service = Service(executable_path='path/to/chromedriver') driver = webdriver.Chrome(service=service)

不过说实话,手动下载驱动、维护版本匹配这件事非常低效。浏览器是自动升级的,今天更新了Chrome,明天驱动就失效了,然后你的脚本莫名其妙报SessionNotCreatedException,原因是Chrome binary version mismatch,翻译成人话就是“浏览器版本太新,驱动不认识它”。

我的建议是直接用webdriver-manager这个库来自动管理驱动版本:

pip install webdriver-manager

然后代码变成这样:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service)

这样它每启动前都会自动检查本地驱动是否与浏览器版本匹配,不匹配就自动去下载正确版本,从此告别“驱动版本不匹配”这个问题。这个是提升幸福指数最明显的一个操作。

注意:国内有些网络环境下,webdriver-manager从Google官方服务器下载驱动可能比较慢,如果卡住了,可以配置使用镜像源,这个主要看你实际的网络情况。

2.3 IDE选哪个,直接影响你排查问题的效率

编辑器选择上,社区里主要两个派系:PyCharm和VS Code。我的个人感受是:PyCharm适合写测试工程、跑pytest、看测试报告,尤其是社区版(免费)已经完全够用。VS Code的优势是轻量、插件生态丰富,适合写小脚本、做快速验证。

如果你用PyCharm,要注意一个细节:新建项目的时候一定要选择已经配置好Python解释器。最好是一开始就用上面建好的虚拟环境(venv目录下的python.exe),不然你命令行里装了一堆包,IDEA里却全标红,根本导入不了selenium。确定解释器路径的操作路径是:File → Settings → Project → Python Interpreter,把你的虚拟环境路径加进去。

VS Code则需要手动配置Python解释器:按Ctrl+Shift+P,输入Python: Select Interpreter,选择虚拟环境的Python版本。

这一步配置好之后,你写from selenium import webdriver的时候就不会看到红色波浪线了。红波浪线看着是小问题,但非常影响判断,因为它会干扰你对“真正错误”的感知。

3. 核心原理拆解:Driver、定位、交互到底是怎么运作的

3.1 WebDriver是怎么控制浏览器的

你要理解Selenium的工作逻辑,只需要记住两个角色:客户端服务端

  • 客户端:你的Python脚本,里面调用selenium.webdriver这个API。
  • 服务端:浏览器驱动(Chromedriver)启动的WebDriver服务。

你的脚本通过HTTP请求给WebDriver发指令:“打开https://example.com”、“找到ID为username的元素”、“输入一段文字”、“点击这个按钮”。WebDriver收到指令之后,用浏览器原生的自动化调试协议(比如Chrome是DevTools Protocol,简称CDP)去驱动浏览器做这些动作。

这就带来一个非常重要的推论:Selenium的等待、查找、执行,都受浏览器当前状态的直接影响。页面还在加载的时候你去找元素,元素还没渲染出来,必然定位失败;页面弹了个原生弹窗,你的脚本卡在那里,也不会有任何报错,就一直等着。这些都不是Selenium本身的问题,而是操作时机和页面状态的问题。

3.2 八种元素定位方式:优先级怎么排

元素定位是所有Web自动化的根基。Selenium提供八种定位方式:

  • find_element(By.ID, "id值")
  • find_element(By.NAME, "name值")
  • find_element(By.CLASS_NAME, "class值")
  • find_element(By.TAG_NAME, "标签名")
  • find_element(By.XPATH, "xpath表达式")
  • find_element(By.CSS_SELECTOR, "css选择器")
  • find_element(By.LINK_TEXT, "链接文字")
  • find_element(By.PARTIAL_LINK_TEXT, "链接文字片段")

实际项目里用得最多的是ID、CSS_SELECTOR、XPATH这仨。优先级的判断标准很简单:标识越唯一,优先级越高。如果一个元素有id,优先用id,因为id在页面里是唯一的;没有id但有class且有业务含义,可以用CSS_SELECTOR;两者都不好使,再上XPATH。

很多人有个误区,就是遇到定位不到的元素就无脑上XPath,然后写出一长串//*[@id="app"]/div[2]/div[3]/div[1]/span这种“脆皮”表达式。这种写法极度依赖页面DOM结构,前端稍微改一版,你的定位就失效了。

我更推荐的做法是:优先给前端提要求,给元素加测试专属的标识,比如>from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait = WebDriverWait(driver, 10) element = wait.until( EC.element_to_be_clickable((By.ID, "submit-btn")) )

常用的条件还有:

  • EC.presence_of_element_located:元素出现在DOM中
  • EC.visibility_of_element_located:元素可见
  • EC.element_to_be_clickable:元素可点击(可见且不禁用)
  • EC.frame_to_be_available_and_switch_to_it:iframe可切入
  • EC.alert_is_present:有弹窗出现

3.4 和页面交互时最容易忽略的两个动作

Selenium的基本交互就是click()send_keys()submit()clear()。但在实际测试里,我发现真正的问题往往出在:

  • 点击之前,没有检查元素是否真的被其他元素遮挡。前端常见的弹窗遮罩、广告浮层、懒加载占位,会让元素哪怕已经出现在DOM里,也被其他层挡住了,此时click会抛出ElementClickInterceptedException或者“静默失败”。这个问题的排查方式是在点击前用element.is_enabled()element.is_displayed()判断,更彻底的办法是在点击前滚动到元素位置,让它的可见区域真正暴露出来,我习惯加一句driver.execute_script("arguments[0].scrollIntoView();", element)

  • send_keys之前,没有清空原有内容。输入框经常自带默认值或者上一次输入残留,直接send_keys会把内容追加在后面而不是替换。稳妥的做法是element.clear()之后再send_keys,或者用ctrl+a全选替换:

element.send_keys(Keys.CONTROL, "a") element.send_keys("新内容")

这些细节看似基础,但在真实测试脚本里出现的频率非常高。

4. 实战场景:从写脚本到处理动态验证码的过程记录

4.1 一个登录用例是怎么从无到有的

为了让你看得更直观,我用一个典型的登录场景完整走一遍流程。需求是这样的:打开一个测试站点,输入用户名和密码,点击登录,验证是否跳转到个人中心。

第一步,初始化浏览器并打开目标URL:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from webdriver_manager.chrome import ChromeDriverManager service = Service(ChromeDriverManager().install()) options = webdriver.ChromeOptions() options.add_argument("--start-maximized") driver = webdriver.Chrome(service=service, options=options) driver.get("https://example.com/login")

建议启动后直接最大化窗口,很多前端样式在非最大化状态下会把部分元素挤出可视区,造成元素不可见不可点。

第二步,定位用户名、密码输入框,填入内容:

wait = WebDriverWait(driver, 10) username_input = wait.until( EC.presence_of_element_located((By.ID, "username")) ) username_input.clear() username_input.send_keys("test_user") password_input = driver.find_element(By.NAME, "password") password_input.clear() password_input.send_keys("passw0rd")

这里我用显式等待等待用户名输入框出现,密码框因为和用户名同属一个form且出现在同一渲染批次里,就直接用find_element,不必每次都显示等待。

第三步,点击登录按钮:

login_btn = wait.until( EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), '登录')]")) ) login_btn.click()

第四步,验证登录是否成功:

wait.until( EC.url_contains("/dashboard") )

只要URL从/login跳到了/dashboard,说明登录成功。当然,也可以直接断言页面里某个“欢迎xxx”的元素出现。

4.2 滑块验证码:自动化测试怎么绕过“人机校验”

这个点是很多搜索记录里反复出现的痛点——“自动化selenium网页拼图验证怎么自动化”、“selenium图片滑块验证”。

先说一个基本态度:滑块验证码出现的目的是区分人与机器,测试场景里正经的解决方案是走“测试环境开关”或找后端加白名单。但如果你的测试环境和真实环境一样都有滑块,那确实需要技术手段去模拟。

滑块拼图验证的核心逻辑其实不复杂:画布上有一张背景图、一个缺口拼图、一个可拖动的滑块。你要做的第一步是计算出滑块需要水平拖动的距离,第二步是模拟人类拖动的轨迹把滑块拖过去。

距离怎么算?最常用的方式是利用PIL图像处理库分析两张图片的像素差异,找到缺口位置。思路是:获取无缺口的完整图片和有缺口的背景图片,逐像素比对,像素差异最大的区域的x轴坐标就是缺口位置。上面这张图一般前端会把它作为canvas绘制出来,你可以通过element.screenshot()把canvas截下来,或者通过canvas.toDataURL()拿到base64图片数据,然后用PIL解析。

计算得到位移之后,模拟拖动的代码大致是:

from selenium.webdriver.common.action_chains import ActionChains import time slider = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "slider-btn"))) ActionChains(driver).click_and_hold(slider).perform() # 分段移动,模拟人的轨迹 step = distance / 10 for i in range(10): ActionChains(driver).move_by_offset(step, random.uniform(-1, 1)).perform() time.sleep(0.05) ActionChains(driver).release().perform()

模拟人类拖动轨迹很关键。你要是用move_by_offset一次性拖到位,后台的风控模型很容易判定你是机器,因为人的手速不可能那么均匀那么直。分段移动、在移动过程中加一点y轴抖动、在接近终点时稍微收一下速度,这三个点模拟出来,通过率会明显高一些。

这里必须提醒一句:滑块验证属于网站的“人机识别”防护,绕过它本身就处在灰色地带。测试环境里调试自动化脚本,没问题;但你拿这套技术去刷票、抢购、批量注册,任何时候都不推荐。做技术的人应该清楚能力边界在哪里。

4.3 文件下载、文件上传的自动化处理

热搜词里有个“selenium怎样使文件下载完成之后才进行下一步”,这个问题出现的频率太高了。很多人用Selenium点击了下载按钮,然后代码直接往下走,结果文件还没下完就去做后续处理了,必然报错。

要处理“等待下载完成”,Selenium本身是没有内置下载完成检测的。常见做法是设置浏览器下载目录,然后在代码里循环检查文件系统:

import os import time download_dir = "/path/to/download" def is_download_finished(directory, timeout=30): start = time.time() while time.time() - start < timeout: files = os.listdir(directory) # 有 .crdownload 或 .tmp 后缀说明还没下完 unfinished = [f for f in files if f.endswith((".crdownload", ".tmp"))] if not unfinished and files: return True time.sleep(1) return False

配合ChromeOptions设置下载目录:

prefs = { "download.default_directory": download_dir, "download.prompt_for_download": False, "download.directory_upgrade": True, "safebrowsing.enabled": True, } options.add_experimental_option("prefs", prefs)

判断逻辑很简单:只要目录里还有.crdownload后缀的临时文件,就说明下载尚未完成,不要开始后续操作。

文件上传就更简单了。页面上的<input type="file">元素,直接用send_keys()把本地文件的绝对路径传进去就行:

file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']") file_input.send_keys("/Users/username/test_report.pdf")

不需要去点那个“上传”按钮再去找系统弹窗,WebDriver协议规定文件输入框可以通过send_keys直接设置文件路径。很多新手不知道这一点,反而卡在“怎么操作系统文件选择框”上。记住这招能省你很多时间。

4.4 iframe和window切换:你找的元素可能在“另一个世界”

页面里还有一类非常隐蔽的坑——iframe。iframe相当于页面里嵌套了另一个页面,如果你不切进这个iframe,你在外层怎么用find_element都找不到里面的元素,哪怕XPath写得再精准也没用。

处理方式:

# 等待iframe可切换 wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID, "frameId"))) # 此时操作iframe内的元素 inner_btn = driver.find_element(By.ID, "inner-button") inner_btn.click() # 操作完后切回主文档 driver.switch_to.default_content()

窗口切换也有类似的逻辑。你点击一个链接打开了新标签页,此时driver还在旧页面,你必须显式切换:

driver.switch_to.window(driver.window_handles[-1])

window_handles是所有窗口句柄的列表,-1代表最后一个新打开的窗口。切换窗口之后还要用driver.close()关闭多余窗口的习惯建议从一开始就养成,不然开着开着几万个标签页堆积在内存里,最后整个浏览器直接卡死。

5. 测试代码的组织方式:从脚本到自动化测试框架

5.1 早期我犯过的错:把所有逻辑写在一个main函数里

如果你只是临时跑一个脚本,把selenium操作全部写在一起问题不大。但在真实项目里,你要维护的是几十上百个用例,这时候脚本式写法带来的问题就非常突出了:重复代码多、用例之间相互影响、报错信息不可读、跑一次要人盯着看

比如你有五个用例都要“登录”这个前置动作,如果每个用例里都复制粘贴一遍登录代码,有一天登录输入框的id变了,你要改五个地方,还很容易漏改。

这就是为什么要从“脚本”走向“框架”,把公共能力抽出来。

我的习惯是把代码分成几层:

  • 页面对象层(Page Object Model):每个页面封装成一个类,页面的元素定位和操作方法都在类里面。
  • 测试用例层:每个以test_开头的函数是一个用例,负责组织操作步骤和断言。
  • 公共组件层:比如driver初始化、等待封装、截图工具、日志工具。

5.2 Page Object Model:页面即对象

先看一个最简单的首页对象:

class LoginPage: def __init__(self, driver): self.driver = driver self.username_input = (By.ID, "username") self.password_input = (By.NAME, "password") self.login_button = (By.XPATH, "//button[contains(text(), '登录')]") def enter_username(self, username): element = self.driver.find_element(*self.username_input) element.clear() element.send_keys(username) def enter_password(self, password): element = self.driver.find_element(*self.password_input) element.clear() element.send_keys(password) def click_login(self): element = self.driver.find_element(*self.login_button) element.click()

将来如果定位方式变了,只需要在这个类里改元组里的值,所有调用这个类的用例都会自动更新。这让维护成本大幅下降。

5.3 pytest:把Selenium包装成真正的自动化测试

pytest是Python生态里最主流的测试框架。它配合Selenium的用法很简单:

import pytest from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service @pytest.fixture def driver(): service = Service(ChromeDriverManager().install()) options = webdriver.ChromeOptions() options.add_argument("--headless") # 如果需要无头模式跑 driver = webdriver.Chrome(service=service, options=options) yield driver driver.quit()

fixture是pytest的核心概念。上面这段代码就是一个fixture,它会在每个测试用例执行前创建driver,用例结束后自动关闭driver,避免浏览器实例泄漏。

然后写用例:

def test_login_success(driver): page = LoginPage(driver) page.enter_username("test_user") page.enter_password("passw0rd") page.click_login() assert driver.current_url == "https://example.com/dashboard"

配合pytest -v跑起来,控制台会输出每个用例的名称和执行结果,绿点是通过,红点是失败。失败的时候还能自动截图保存现场,这个是排查问题的利器。

5.4 失败截图为什么是必加项

UI自动化最痛苦的事情是什么?不是脚本报错,而是脚本报错你看不到页面当时的状况。等你登录到远程机器上看的时候,页面状态早就变了。所以在一套完整的框架里,失败自动截图几乎是必须的。

pytest里可以用conftest.py定义pytest_runtest_makereport钩子:

import pytest from pathlib import Path @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item): outcome = yield report = outcome.get_result() if report.when == "call" and report.failed: driver = item.funcargs.get("driver") if driver: screenshot_dir = Path("screenshots") screenshot_dir.mkdir(exist_ok=True) driver.save_screenshot(str(screenshot_dir / f"{item.name}.png"))

这样一旦用例失败,就会自动保存一张失败时的页面截图到screenhots目录,方便你快速定位是页面崩了、元素没加载出来,还是断言条件不满足。

5.5 无头模式与并行执行

真实项目里跑UI自动化,通常不会开着浏览器让你肉眼盯着。CI环境(持续集成)里跑测试,浏览器界面根本没有硬件加速,需要用到--headless无头模式:

options.add_argument("--headless=new") options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage")

注意,无头模式下要注意两点:一是无头模式依然是真实浏览器内核,只是不渲染窗口,所以CSS渲染结果和正常模式基本一致;二是无头模式对动画、懒加载的处理有时和正常模式有细微差异,如果发现某个用例只在某种模式下失败,先别急着怀疑浏览器,优先怀疑是不是等待条件写得太脆了。

并行执行可以用pytest-xdist插件:

pip install pytest-xdist pytest -n 4

这样4个用例可以同时在4个线程里跑。并行虽然能显著缩短总体执行时间,但对被测系统的并发承受能力也是个考验,如果被测系统扛不住,并行反而会造成大量误报,所以加并行要谨慎。

6. 踩坑复盘:这几个错误我用坏了好几个浏览器Driver

6.1 坑位一:定位方式写得“太聪明”

有阵子我为了图省事,经常用contains(text(), 'xxx')去匹配元素,比如:

driver.find_element(By.XPATH, "//div[contains(text(), '确认')]")

刚开始一切正常,后来前端结构调整,页面里多个元素都包含“确认”两个字,find_element默认返回第一个匹配的元素,结果点了不该点的按钮,测试用例用例全都串了。

从那以后我给自己立了个规矩:能用位置关系表达的,不要依赖文本;必须依赖文本的时候,用精确匹配而不是contains

driver.find_element(By.XPATH, "//button[text()='确认']")

如果是动态生成的部分匹配,至少要拼上父级节点做范围限定。

6.2 坑位二:点击失效但不报错

有一种非常磨人的情况:定位到了元素,click也没有抛异常,但页面没有任何反应。这种“静默失败”比直接报错更让人崩溃。

排查思路是有层次的。先看元素是不是被遮挡,用element.get_attribute('class')看样式类里是不是有disabled状态;再看是不是有元素正在覆盖它,可以用JavaScript执行一次强制点击做交叉验证:

driver.execute_script("arguments[0].click();", element)

如果强制点击生效,说明就是遮挡问题,回头检查等待条件是否合理,或者需要先关闭弹层/遮罩。如果强制点击也不生效,那就是元素绑定的JavaScript事件没有正确初始化,这种情况要考虑刷新页面或者调整操作顺序。

6.3 坑位三:浏览器进程残留

Windows环境下,测试脚本异常退出之后,chromedriver进程经常不会自动结束,还会连带一堆chrome的僵尸进程留在后台。再跑脚本的时候,新启动的Chrome会被残留进程影响,最常见的问题是端口被占用导致WebDriver启动失败。

这时候最简单的处理方式就是脚本入口处加一个进程清理逻辑:

taskkill /F /IM chromedriver.exe /T taskkill /F /IM chrome.exe /T

Linux/Mac下对应的是:

pkill -f chromedriver pkill -f chrome

更好的做法是代码里用finallyfixture的teardown保证driver.quit()一定会被调用。

6.4 坑位四:懒加载导致元素渲染时间不可控

现在的网页前端大量使用懒加载,图片、下拉列表、列表数据,往往要滚动到可视区域才会发起网络请求加载。你直接driver.get(url)之后立刻去定位页脚的数据,十次有九次找不到。

解决方案是滚动页面触发加载,再等待条件满足:

driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

或者更细致一点,指定滚动到某个具体元素的位置:

element = driver.find_element(By.CSS_SELECTOR, ".footer") driver.execute_script("arguments[0].scrollIntoView();", element)

滚动之后不要立刻去找元素,加一个短暂的显式等待,比如等待目标元素可见,再去操作。

数据加载的场景里,我习惯用“数据占位符出现→数据占位符消失→断言出现内容”这种三步判断,比单纯等固定几秒靠谱得多。一般加载状态会有loading动画或者占位符,等待它消失再断言,稳定性会明显提升。

7. 最后分享几个我用了一年才养成的测试习惯

说几个不在任何教程里、但实际帮了我大忙的习惯。

第一,每一条测试用例必须能独立运行,不依赖其他用例的执行顺序。什么叫“不依赖”?就是你可以单独跑pytest test_login.py::test_login_success,它也能按预期通过。如果用例之间互相依赖(比如A用例创建的数据B用例要用),那你跑全量的时候没问题,但一旦断点调试或者指定运行某条用例,就会莫名其妙挂掉,排查成本极高。这个习惯一旦养成,你的测试用例可维护性会上一个台阶。

第二,在操作前想清楚“这条用例的核心断言到底是什么”。UI自动化非常容易写成“点了这个按钮再点那个按钮”,最后什么都没验证。好的用例一定要有一个明确的断言——要么是页面跳转了,要么是弹窗提示出现了,要么是数据发生了变化。没有断言的用例等于白跑。

第三,高频操作务必封装,封装接口的统一返回值很重要。拿点击登录按钮来说,封装方法的返回值最好设计成“登录结果页的对象”,而不是“None”。这样用例写起来会非常舒服,也是往Page Object方向自然演化的路径。

第四,调试阶段用有头模式,定期跑一次无头模式。有头模式方便你直观地看到浏览器在做什么,定位问题非常高效;但发布到CI之前一定至少要跑一轮无头模式,确保两种模式下行为一致。

做Web自动化测试,真正决定你效率上限的,从来不是会不会调用Selenium的API,而是你能不能稳定地处理等待、定位、异常这三件事。Selenium的API文档很薄,但它在真实页面上的表现,会把你对Web前端的理解、对浏览器原理的认知、对异常处理的敏感度全都逼出来。这篇文章里的每一个坑,都是我拿实际项目时间换来的,希望能帮你少走几步弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 5:42:55

医学影像分析实战:ResNet、UNet、DeepLabV3+与YOLOv5技术指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 5:42:44

OTA升级性能测试全解析:从数据采集到优化实践

这次我们来看一个关于OTA升级后性能表现的技术分析项目。从标题"OTA之后的白幽灵果然夯&#xff01;【彬彬一周数据】"来看&#xff0c;这应该是一个针对某款代号"白幽灵"的设备或系统在OTA更新后的性能测试和数据报告。 这个项目的核心价值在于提供了真实…

作者头像 李华
网站建设 2026/9/8 5:40:50

HPC负载均衡实战:调度、网络、存储与应用层全解析

做过高性能计算&#xff08;HPC&#xff09;集群运维的朋友&#xff0c;大概率都遇到过这种场景&#xff1a;明明所有节点的CPU型号、内存大小一模一样&#xff0c;跑同一个算例脚本&#xff0c;有的节点几分钟就交差了&#xff0c;有的节点却直接干到超时被杀。我再翻调度日志…

作者头像 李华
网站建设 2026/9/8 5:39:12

uniapp + Vue3 父子组件通信实战:props、emit 与 defineExpose 完整指南

1. 从Unix的组合思想说起&#xff1a;为什么父子通信值得单独研究去年我在做一个跨端项目&#xff0c;技术栈是 uniapp vue3&#xff0c;页面拆了十几个组件&#xff0c;功能本身不难&#xff0c;但持续迭代两三个月后&#xff0c;我发现自己大量时间不是在写业务&#xff0c;…

作者头像 李华
网站建设 2026/9/8 5:38:26

AI时代开发者专注力挑战与可落地的技术解决方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 5:38:16

AI视频批量生产全自动化:Claude+H Higgsfield+ffmpeg流水线实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华