☰
Selenium自动化测试实战:从元素定位到稳定性提升的踩坑指南
2026/10/20 10:10:14 网站建设 项目流程

在测试行业里摸爬滚打几年之后,我越来越确信一件事:Selenium 自动化测试的难点从来不是语法,而是对真实业务场景的理解、对不稳定因素的预判,以及对每一个坑的排查能力。网上一搜“Selenium 实战”,铺天盖地都是“登录后打印 title”这种入门 Demo,真正能解决工作中“页面时不时抽风”“元素明明存在却定位不到”“跑一轮脚本要两个小时”这些问题的案例,反而少得可怜。

所以当我看到“全网最全,Selenium 自动化测试实战案例”这个标题时,我的第一反应是:这不该是一篇堆砌代码的开源文档,而应该是一份从踩坑中长出来的实战地图。这篇文章我会按自己的经验来写,重点放在“为什么这么做”和“遇到问题怎么办”上。不管你是刚接触自动化的测试新人,还是已经写了不少脚本但总被稳定性折磨的工程师,应该都能从中找到一些可以直接抄作业、改改就能用的东西。

1. 内容整体设计与思路拆解

动手写任何 Selenium 脚本之前,我都会先逼自己回答三个问题:这个任务真的适合用 Selenium 做吗?我选哪种方式去定位元素最稳?脚本跑完我要怎么确认它是成功的,而不是“看起来成功了”?这三个问题想不清楚,后面的代码写得再漂亮也白搭。

1.1 为什么绝大多数交互验证类任务绕不开 Selenium

Selenium 的核心价值在于它能模拟真实用户在浏览器里的所有操作:点击、输入、拖拽、切换标签页、处理弹窗、执行 JavaScript。这意味着它不仅能做单元测试和接口测试做不了的事情——比如验证一个前端按钮的样式是否正确、一个拖拽交互是否触发回调、一个支付流程的完整链路是否顺畅——还能用来做跨浏览器兼容性验证。

我做过的几个项目里,最有代表性的一个是一个多端登录系统。接口测试覆盖了所有 API 的正常和异常逻辑,但轮到一个“记住密码后刷新页面,表单是否自动回填并且验证码状态是否正确”的需求时,接口测试完全无能为力。这时候 Selenium 的价值就体现出来了:它可以输入一次账号密码,勾选记住我,刷新页面的语气和用户完全一致,然后断言输入框里的 value 是否被回填。

但我也要泼一盆冷水:Selenium 不适合做高并发压测。大量启动浏览器实例会吃光机器内存,而且它的定位机制本身就有毫秒级开销,跟 Locust、JMeter 这类并发工具干的事完全不是一回事。正确的姿势是把它跟接口测试、性能测试组合成一套立体化的质量保障体系,各自承担各自擅长的部分。

1.2 方案选型的核心逻辑:稳定优先的三种定位策略

“全网最全”这四个字我之前也信过,后来发现凡是号称“最全”的往往最容易被细节击败。拿元素定位来说,很多人一上来就复制 XPath,结果前端稍微加个 span,整个脚本当场报废。我的经验是,定位策略的优先级基本可以固定为:能用 ID 用 ID,ID 不稳定就优先用相对 XPath 或者 CSS 配合文本内容,最后才考虑绝对 XPath。绝对 XPath 就像是把回家的路写成“出小区门左转第三个路口再右转再走 200 米遇到红绿灯再左转”,但凡路上多一个摊位,你就回不了家。

CSS 和相对 XPath 的选择也不能一刀切。CSS 选择器在大多数浏览器里执行速度更快,语法更简洁,适合属性定位和父子层级定位;XPath 的优点是可以通过文本内容、包含关系、轴定位(比如找父节点的兄弟节点)做更复杂的查询。我的做法是:结构稳定时用 CSS,文本不稳定但层级稳定时用相对 XPath,定位不到时再用 JavaScript 配合执行脚本辅助。

1.3 脚本的可维护性设计:比跑通更重要的是防失控

很多人写自动化脚本,思维还停留在“写一段完成一个动作”上面,这就会导致每加一个新需求,就要把所有代码翻出来重新改一遍。我在实际项目中坚持的方式是:把脚本拆成三层。第一层叫操作层,封装所有对单个元素的动作,比如输入、点击、下拉选择、文件上传;第二层叫业务层,把常用的业务流程组合起来,比如“完成一次带优惠券的下单”;第三层是测试层,只负责组织测试数据、调用业务层、做断言和报告。

这套分层思路,加上“每个元素定位表达式都存成单独的变量”,能让脚本的可读性和可维护性提升非常多。有一次某平台前端在一个月里改了三次 DOM 结构,我的业务层和测试层代码一行没动,只修改了元素定位常量表就完成了全部适配。要是当初把所有 find_element 写在同一个脚本里,那三次改版我就得三次重写整份代码,工作量完全不在一个量级上。

2. 环境准备与工具链选型

写 Selenium 爬坑的第一步,是先让环境稳定下来。Chrome 和 ChromeDriver 的版本不匹配,是新手最常踩的第一个大坑。我见过太多人代码写得没问题,但一跑就报SessionNotCreatedException,原因就是本地 Chrome 自动升级到了 120 版本,而 ChromeDriver 还停在 114。

2.1 浏览器、驱动与依赖的版本匹配方案

最简单的解决办法是引入 WebDriver Manager 这样的依赖库。它会在第一次运行时自动访问浏览器的版本接口,下载匹配的驱动文件,彻底告别手动找版本的日子。这里拿 Python 生态举例,pip 安装 selenium 之后,再装一个 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)

这个方案最大优势是避免版本匹配问题,同时驱动文件会统一放在本机缓存目录里,团队协作时不会出现“你本地能用我本地报错”的尴尬。如果是 Java 生态,同样也有对应的 WebDriverManager 库,用法类似。

不过我有一次还遇到过更诡异的情况:ChromeDriver 版本匹配,但打开页面直接白屏。排查后发现是 Chrome 在静默安装一个小版本更新的时候锁住了用户数据目录,导致启动时初始化失败。解决方式是在启动参数里加上一个隔离的 user-data-dir,避免跟现有浏览器实例抢资源。

2.2 启动参数的合理配置:速度与稳定性的平衡

很多新手写 Selenium 从来不配启动参数,默认设置下浏览器窗口会弹出来,干扰正常使用,而且脚本跑起来明显感觉“笨重”。我一般在初始化浏览器的时候就会把下面这些参数加进去:

options = webdriver.ChromeOptions() options.add_argument("--window-size=1920,1080") options.add_argument("--disable-gpu") options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") # 如果不需要看页面,加上下面这行 options.add_argument("--headless=new") driver = webdriver.Chrome(options=options)

--headless=new是无头模式,跑完整个页面不会弹出窗口,适合集成到 CI 流水线里定时执行。但无头模式下,一部分浏览器的特性会表现跟有头模式不同,比如某些图形验证码的渲染或某些需要拖拽选区的操作,可能出现异常。所以我的经验是:调试阶段不要开 headless,等到脚本确认稳定了再切换成无头模式跑批量任务,这样既能看清问题,又能提升效率。

2.3 语言选型的个人建议

Selenium 官方支持 Java、Python、C#、Ruby、JavaScript 等多种语言。如果你所在团队已经有统一的测试技术栈,那直接跟团队保持一致即可;如果是从零开始搭,我建议无脑选 Python。原因有三条:第一,Python 的 selenium 库和周边生态最丰富,遇到问题能搜到的现成解决方案最多;第二,它能把“写页面自动化”的成本降得非常低,写起来自然流畅;第三,Python 的数据处理能力很强,同理很适合做后续的数据校验。

Java 的好处在于工程化能力更强,适合跟现有的微服务测试框架深度集成。我后来在一个大项目里就同时存在两套 Selenium 脚本,Python 脚本用来快速验证线上功能的 bug 复现,Java 脚本跑比较正式的全链路回归,各有各的定位。

3. 核心实战案例:从登录到数据采集的完整实现

我挑了三个出现频率非常高的实战场景,分别是登录表单处理、页面数据采集、复杂交互模拟。这三个场景基本覆盖了日常工作中 Selenium 用得最多的那部分。

3.1 案例一:登录流程与表单填充的细节处理

登录是所有自动化脚本的基础,也是问题最多的环节。普通的登录脚本只有三步:定位输入框、输入内容、点击登录。但真实世界的登录远没有这么单纯,会遇到“输入框被隐藏”“验证码突然弹出”“登录按钮置灰不可点击”等各种怪事。

先看一个典型的表单填充片段:

from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等输入框可见后再操作 username = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "username")) ) username.clear() username.send_keys("test_user") password = driver.find_element(By.CSS_SELECTOR, "input[name='password']") password.clear() password.send_keys("pass123") login_button = driver.find_element(By.XPATH, "//button[contains(text(), '登录') and not(contains(@class, 'disabled'))]") login_button.click()

这里的关键是:先显式等待输入框变成可点击状态,再执行 clear 和 send_keys。clear 这一步很重要,因为很多前端脚本会在输入框里预填一个提示文案,如果不清理直接输入,最后提交的表单字段就会带着旧值。我踩过这种坑,页面里有“请输入手机号”的占位符文字,看起来是灰的,但它并不是 placeholder,而是真实存在于 input 里的 value,导致后面断言账号信息一直不对。

至于验证码,如果是图形验证码,最稳妥的方式是接入打码平台或者让测试环境关闭验证码。千万不要想着自己训练一个 OCR 模型来识别,投入产出比太低。我在一个模拟项目 X 的处理上用过打码平台接口,直接调用接口返回坐标,再结合 ActionChains 完成点击,流程大概是:截图验证码区域、请求识别接口、拿到坐标、点击图片对应位置。

3.2 案例二:数据采集与分页、懒加载处理

Selenium 采集数据的核心优势是能拿到动态渲染出来的数据,比如页面是 Vue 或 React 框架,通过 Ajax 请求动态填充表格,requests 拿到的 HTML 里根本没有这些内容。

采集的典型流程是这样的:

all_data = [] # 处理分页 while True: WebDriverWait(driver, 10).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".data-row")) ) rows = driver.find_elements(By.CSS_SELECTOR, ".data-row") for row in rows: name = row.find_element(By.CSS_SELECTOR, ".name").text price = row.find_element(By.CSS_SELECTOR, ".price").text all_data.append({"name": name, "price": price}) next_btn = driver.find_element(By.CSS_SELECTOR, ".next-page") # 如果下一页按钮不可用,说明已经到最后一页 if "disabled" in next_btn.get_attribute("class"): break next_btn.click() time.sleep(1)

这里time.sleep(1)看起来很简单,但却是很多人的痛点来源:有的人写了个sleep(10),把速度拖得很烂;有的人完全去掉等待,导致列表还没渲染完就去点下一页,结果定位到的还是上一页的元素。更稳的做法是通过显式等待配合页面 URL 变化或列表元素重新加载来做,比如记录列表第一条数据的内容,在点击下一页之后等待它的内容发生变化。

懒加载是另一个常见问题。页面一开始只渲染了前 10 条数据,需要滚动到底部才能加载更多。解决方式是反复执行 JavaScript 滚动操作,直到页面高度不再变化:

last_height = driver.execute_script("return document.body.scrollHeight") while True: driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") time.sleep(2) new_height = driver.execute_script("return document.body.scrollHeight") if new_height == last_height: break last_height = new_height

这套逻辑在做信息流类采集的时候非常实用。需要注意的是,页面里如果存在“加载更多”按钮,滚动到底部之后可能还需要额外点击按钮,判断逻辑要相应地加上。

3.3 案例三:文件上传与复杂交互的 ActionChains 用法

文件上传是个很容易踩坑的点。HTML 标准的上传控件是 input[type=file],直接用 send_keys 写入本地文件路径就能完成上传,这个很多人都知道。但有些前端框架把 input 隐藏了,只留一个按钮,此时就不能直接定位 input,得先把隐藏元素找出来,再执行send_keys,或者用driver.execute_script直接赋值文件路径。

拖拽操作则需要用到 ActionChains:

from selenium.webdriver.common.action_chains import ActionChains source = driver.find_element(By.ID, "drag-source") target = driver.find_element(By.ID, "drop-target") actions = ActionChains(driver) actions.drag_and_drop(source, target).perform()

我工作中做过一个图像标注工具的自动化测试,需要在画布上模拟鼠标框选区域。这个操作比普通拖拽复杂,因为它需要按下左键、移动一段距离、松开,还要保证中间不能有其他的鼠标事件干扰:

canvas = driver.find_element(By.CSS_SELECTOR, ".annotation-canvas") actions = ActionChains(driver) actions.move_to_element(canvas) actions.click_and_hold() actions.move_by_offset(120, 80) actions.release() actions.perform()

为了确保偏移量正确,我还专门在调试时开启了浏览器的鼠标追踪日志,发现实际偏移跟预期有偏差,原因是画布元素外层有一个滚动条,move_to_element 只保证鼠标移动到元素中心,后面 move_by_offset 是相对当前鼠标位置移动的,所以必须先计算画布相对于视口的初始位置。这个细节调试了我整整半天,写下来就是一句话:拖拽前先确定鼠标的当前锚点,再决定偏移量,而不是想当然地从元素左上角开始算。

4. 定位策略与等待机制详解

经常有人问我:为什么我的脚本在本地跑得好好的,一到服务器上就疯狂报“元素找不到”?答案往往有两个:一个是元素还没有出现就被定位了,另一个是页面里存在多个相同属性的元素,定位到了错误的那个。等待机制的掌握和定位技巧的提升,能直接解决大半的稳定性问题。

4.1 显式等待和隐式等待的正确用法

隐式等待的代码最简单:driver.implicitly_wait(10),它表示每次查找元素的时候,如果元素没出现,最多会等待10秒。但这种全局设置会作用于所有查找操作,副作用是明明一个元素永远不会出现,也会傻等10秒才抛异常,拖慢整体速度。

显式等待则可以根据单个元素的具体情况来设置超时时间。我推荐的做法是:全局设置一个较短的隐式等待(比如 5 秒),然后关键步骤用显式等待来覆盖。显式等待我最常用的是这几种 condition:

# 等待元素可见 WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "submit"))) # 等待元素可点击 WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//button[text()='确认']"))) # 等待元素消失,适合弹窗关闭的场景 WebDriverWait(driver, 10).until(EC.invisibility_of_element_located((By.CLASS_NAME, "loading"))) # 等待文本出现在某个元素里 WebDriverWait(driver, 10).until(EC.text_to_be_present_in_element((By.ID, "status"), "成功"))

这里有一个底层机制值得说透:WebDriverWait 默认每 0.5 秒轮询一次 condition,如果 condition 一直不满足,就会抛出 TimeoutException。理解这个轮询机制,你就能意识到不要随便把轮询间隔改大,因为默认的 0.5 秒已经能在稳定性和速度之间取得不错的平衡。真正需要调的往往是超时时间,不同场景要不同设置。

4.2 元素定位经常踩的三个坑

第一个坑是find_element与find_elements的误用。find_element找不到时直接抛异常,而find_elements找不到时返回空列表。所以我习惯用find_elements做存在性判断,特别适合判断一次性弹窗是不是已经出现了,比如“新版本引导浮层”。

第二个坑是元素属性重复。页面上同一 class 的元素可能有好几个,这时必须组合多个维度去定位,比如“包含某个文本的按钮”或“位于某个容器内的链接”。我曾遇到过一种情况,页面上有两个操作按钮,一个叫“确认收货”,一个叫“确认订单”,我写 XPath 时用了//button[contains(text(), '确认')],结果每次都点到了第一个按钮。后来我把 XPath 改成精确匹配 text() 的值,才彻底解决这个问题。

第三个坑是元素不在视口内。页面很长时,要通过scroll_into_view把元素滚到可视区域后再操作。不滚动直接点击,部分浏览器会提示“element not interactable”。

element = driver.find_element(By.ID, "submit-btn") driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element) element.click()

这一步在真实性操作里极其重要。很多流程里点不到的按钮,都是因为没有先滚动到它所在的位置。

4.3 页面加载策略的调优技巧

默认情况下,driver.get()会等待页面所有静态资源加载完才能返回。如果某个页面里有个第三方统计脚本几十秒都加载不出来,整个脚本就会卡死在那里。这时可以通过driver.set_page_load_timeout(10)来做限制,超过 10 秒就抛超时异常,再结合页面关键元素的显式等待来兜底,确保核心内容已经渲染。

另外还有一个模式叫 eager,它只等 HTML 文档加载完成,不会等待图片等子资源。设置方式是在第一次启动 Chrome 的时候通过 options 传入:

options.page_load_strategy = "eager"

这个策略适合数据接口已经返回、但图片还没加载完的页面。因为它能明显缩短等待时间,我用过一段时间,后来发现某些页面会因此导致后续点击的按钮还没绑定事件,所以最后我还是回到了 normal 加载策略加上显式等待的组合,稳妥优先。

5. 常见问题与排查技巧实录

这一节都是我从真实项目里一点点记下来的,每一条背后都有一段抓耳挠腮的经历。把它们整理成速查表,希望能帮你省下那些绕远路的时间。

5.1 高频异常的原因与处置办法

异常类型常见原因解决办法
NoSuchElementException元素不存在、定位表达式错误、元素没渲染完检查表达式;加显式等待;用 find_elements 做存在性判断
ElementNotInteractableException元素被遮挡、不可见、或者处于禁用状态滚动到元素所在位置;等可见后再点击;检查有无弹窗遮挡
StaleElementReferenceException元素已经被页面重新渲染,之前拿到的引用失效重新定位元素;避免在循环里缓存元素对象
TimeoutException等待条件长时间未满足确认等待的 condition 是否正确;检查页面是否有错误提示
SessionNotCreatedException驱动版本与浏览器版本不匹配用 WebDriver Manager 或手动同步版本
InvalidSelectorExceptionXPath/CSS 语法写错在开发者工具里先验证表达式;注意特殊字符转义

StaleElementReferenceException是循环采集中最容易踩的雷。比如你用 find_elements 拿到一个列表并遍历,但遍历过程中页面执行了局部刷新,那之前保存的那些元素引用就全部失效了。解决办法是改成每次遍历都重新获取元素集合,或者把需要的数据先提取出来存到本地变量里,再去操作另一个页面的东西。

5.2 脚本运行速度的优化思路

很多人写的脚本跑得特别慢,跑一轮两千个用例得四五个小时。优化思路主要有三个维度。

第一是减少不必要的等待。把所有的time.sleep(3)换成显式等待,至少能提速 30%。第二是减少不必要的页面跳转,能用新标签页打开的绝不关闭再打开。第三是尽量复用浏览器的登录态,比如在命令行参数里直接加载一个已经登录过的 Chrome 用户数据目录,或者把登录之后的 Cookie 持久化,下次启动直接注入,免得每个用例都重新登录。

import pickle # 保存登录态 cookies = driver.get_cookies() with open("cookies.pkl", "wb") as f: pickle.dump(cookies, f) # 下次启动时注入 with open("cookies.pkl", "rb") as f: cookies = pickle.load(f) for cookie in cookies: driver.add_cookie(cookie)

这里要记住一个细节:add_cookie 之前必须先访问一次目标域名,让浏览器拥有该域名的会话上下文,直接 add_cookie 会报“cannot set cookie for non-current domain”。

5.3 反爬与风控遇到时的应对策略

页面自动化遇到风控的概率很高,尤其是采集类任务。我的应对思路是尽量模拟真实用户行为,而不是硬刚。

首先,设置浏览器的 User-Agent,默认的 headless UA 太容易被识别了。可以用options.add_argument('--user-agent=Mozilla/5.0 ...')覆盖掉。其次,控制操作节奏,每次请求之间加随机的 1 到 3 秒延迟,不要让脚本像一个没有呼吸的机器人一样卡着固定的间隔操作。第三步,规避浏览器指纹检测。部分站点会检查navigator.webdriver属性是否为 true,可以用执行脚本的方式把这个属性改掉:

driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" })

这个方法对基于 Chromium 的浏览器有效,目的是把 webdriver 标记隐藏起来。不过我还是要声明一句:这些技巧只应该用在合法合规的测试和自有业务的数据校验上,用来做违反平台规则的事,不但没有价值,还会给自己惹麻烦。

5.4 结果断言与失败重试机制

自动化测试的价值在于发现缺陷,而不只是跑通脚本。我在每个关键流程结束之后都会加断言,比如登录成功后验证页面 URL 变化、用户昵称是否展示、数据库状态是否正确。断言的粒度要控制在“能定位问题”的程度,不要什么都断言,否则任何一个前端样式调整都会把整条流程打断。

失败重试机制的实现方式也很简单:用一个装饰器包住测试函数,遇到断言失败或者元素超时异常时重新执行整个流程。我在一个高频业务系统里的做法是重试 2 次,每次重试前清理浏览器缓存并重启一个全新的会话,确保不会受到上一次失败的遗留状态影响。这个机制上线以后,脚本的稳定性从 70% 提升到了 98% 左右,效果非常明显。

6. 项目工程化落地与后续扩展

脚本能跑通是一回事,能在团队里长期维护、稳定运行是另一回事。这里写下我在工程化落地上的一些体会。

6.1 测试报告的生成与定时触发

测试报告我基本会走两种形式。本地调试时用健壮性比较高的 HTML 报告,直接集成 pytest-html 或者 Allure;CI 流水线里则让 pytest 把结果输出成 JUnit XML,然后由流水线的插件去解析、展示趋势图。这里有个细节:报告里要尽量包含失败截图,否则出现问题以后,开发根本不知道现场长什么样。截图逻辑我一般写在如下钩子里:

import allure def on_test_failure(): screenshot = driver.get_screenshot_as_png() allure.attach(screenshot, name="failure_screenshot", attachment_type=allure.attachment_type.PNG)

定时触发可以用 Jenkins 的定时任务,也可以用操作系统的 cron。定时任务跑的时候,确保机器空闲内存充足,同时设置浏览器实例数量的上限,防止残留的 chrome 进程把系统资源耗光。

6.2 与接口测试、手动测试的分工融合

一个人写自动化脚本,如果没有全局观,很容易陷入“凡是 UI 能操作的都要自动化”的误区。我个人的判断标准是:核心业务主链路和影响范围广的场景,一定要自动化;边缘的、偶发的、需要人工视觉判断的,不要硬自动化。UI 自动化的定位是“像用户一样检验前端交互”,而数据正确性、权限校验这些更适合用接口测试来覆盖。两者组合,配合少量人工探索式测试,才能形成完整闭环。

6.3 脚本从本地扩展到分布式执行的思路

做跨平台或跨设备测试时,单机跑脚本就不太够用了。最稳妥的扩展方式是用 Selenium Grid,把不同的机器注册成节点,主节点按需分配任务。Selenium Grid 4 的部署相比以前简单得多,一条命令就能启动 hub,然后各台机器上跑一条命令注册节点。

我在一个某跨平台系统的回归项目里,用 Grid 把 8 台机器组织成一个执行集群,跑 1200 个用例的时间从 70 分钟压缩到了 15 分钟以内。配置的关键点是要确保各节点上的浏览器驱动、Python 依赖版本完全一致,否则就会出现“同一个用例在这个节点上通过,在那个节点上失败”的灵异现象。解决方式是做一个 Docker 镜像,所有节点都用同一个镜像拉起执行环境。

到了这一步,Selenium 自动化再也不是那个“只能自己电脑上跑跑的小玩具”,而是一套跟 CI/CD 流水线深度集成的质量基础设施。我从刚开始调 ChromeDriver 版本调到怀疑人生,到现在闭上眼睛都能写出稳定脚本,最大的感触是:自动化测试拼的不是花哨的奇技淫巧,而是能不能用工程化的思维把不确定性管理起来。

最后分享一个个人很受用的习惯:每到一个新项目,我会把项目里那些难定位的元素、经常超时的场景、容易触发风控的操作,单独记录成一个经验文档。等踩过的坑积累到几十条,那份文档比任何“全网最全”的教程都有价值。Selenium 本身更新得很快,浏览器也在天天变,但底层那些“稳定性优先、显式等待、分层维护、失败可诊断”的思路,换什么工具都不过时。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询