1. 先把这件事想清楚:Selenium元素操作到底在解决什么问题
做自动化测试也好,写爬虫也好,接触Selenium的第一道坎几乎都是元素操作。原因很简单:所有后续动作——点击、输入、拖拽、断言——都建立在“你能找到那个元素”这个前提上。元素定位不稳,后面所有代码都是空中楼阁,今天能跑明天就挂,这在业界叫“脆弱的自动化”。
我见过不少同学学Selenium,上来就死记XPath语法,背完发现实际项目里照样抓瞎。问题不出在语法,出在没理解定位的本质:我们在模拟真实用户的眼睛。真实用户靠视觉找按钮,Selenium靠DOM结构里的特征找节点。所以定位策略的核心,就是找一组在目标页面上足够稳定、足够唯一的特征,这组特征还要能扛住页面改版。
这篇文章基于我在HoRain云环境里做Web自动化测试的实践经验,把从环境搭建、浏览器驱动选型、定位策略到高效交互的完整链路串起来讲。重点放在“为什么这么选”和“哪些坑我提前替你踩过了”,代码用Python写,因为目前测试圈和爬虫圈用Python+Selenium的组合最主流,遇到问题搜解决方案也最容易。
适合谁看:刚开始学Selenium的测试新人、想用Selenium做数据采集的爬虫开发者、以及写自动化脚本总遇到“元素找不到”报错的老手。看完你至少能解决80%的定位和交互问题。
2. 环境准备与浏览器驱动选型,这一步错了后面全白搭
2.1 Selenium安装其实没什么好纠结的
安装本身一句话就够:pip install selenium。但很多人装完就跑demo,跑完第一次能开浏览器,第二次就报错,绝大多数问题出在驱动上。Selenium本身只是个自动化协议客户端,真正去操控浏览器的是各个浏览器厂商提供的Driver,Chrome叫chromedriver,Firefox叫geckodriver,Edge叫msedgedriver。
这里有个容易忽略的点:Selenium 4.x和3.x的某些API有差异。比如定位元素,3.x的find_element_by_id写法在4.x里被标记为废弃,推荐换成find_element(By.ID, "xxx")这种写法。网上大量教程还是老写法,你照抄完发现自己的Selenium 4.10版本跑不起来,不是代码错了,是版本差异。建议装新版,然后统一用By这个枚举类,兼容性最好。
2.2 浏览器驱动到底怎么判断下载哪个,全网最乱的问题
“web自动化selenium浏览器驱动怎么判断下载哪个区别”这个搜索词天天有人查,我直接给结论。Chrome浏览器点右上角三个点,进“关于Chrome”,能看到类似“版本 126.0.6478.126”这样的信息。然后去chromedriver的下载列表里,找和你主版本号一致的最新版。主版本号就是第一个数字,126对126,不是非要精确到后面。
这里有个特例必须提醒:Chrome更新很勤快,有时Chromedriver还没跟上最新版,这时候你只需要找到最接近且小于你浏览器版本号的驱动,通常能正常工作。反过来,如果你用Selenium Manager(Selenium 4.6起内置),它会自动帮忙匹配驱动,但国内网络环境下有时下载慢或失败,所以手动下载驱动放到项目目录下,再用service = Service("chromedriver路径")指定,这种做法最可控。
Firefox和Edge同理,主版本号匹配即可。忘了自己装没装驱动,命令行敲chromedriver --version,能输出版本号就没问题。
2.3 快速验证环境可用的三行代码
环境装没装对,别急着写复杂脚本,先跑最小用例:
from selenium import webdriver driver = webdriver.Chrome() driver.get("https://www.example.com") print(driver.title) driver.quit()能弹出浏览器、能打印出页面标题、能正常关闭,说明Selenium、驱动、浏览器三者已经打通。这一步跑不过,后面写再多都是白费。跑不过的时候按顺序排查:Python环境是否正常、selenium是否装进当前解释器、chromedriver是否在PATH里或路径是否正确、浏览器和驱动主版本号是否一致。我自己遇到过最傻的情况:系统里有两个Python环境,pip install装到A环境,脚本用B环境跑,折腾半小时才反应过来。
3. 元素定位八种武器,实际项目中怎么选才不翻车
3.1 ID定位:能用的优先用,没有就造一个
页面元素的id属性在HTML规范里要求唯一,所以find_element(By.ID, "login-btn")是性能最好、最稳定的定位方式。我写自动化脚本的第一原则:元素有id就用id,没有id再看其他。这不是因为我懒,而是id是前端工程师最常设置、最不容易重复的属性。
但现实是全国统一的前端框架下,很多团队写代码时id起得随意,比如btn1、btn2这种,页面一改版就迁走。遇到这种情况,我不建议硬啃id,可以看一下元素附近有没有稳定的父节点,用相对定位去锁定。另外有些id是动态生成的,比如带时间戳或随机数的,每次刷新都变,这种id等于没有,别浪费时间。
3.2 XPath定位:不要只会复制,要会写“稳”的表达式
XPath是万金油,因为它能表达任意复杂的关系:兄弟节点、父节点、文本内容、属性组合。浏览器devtools里右键元素可以直接复制XPath,但复制出来的通常是绝对路径,又长又脆。我偏好手写相对XPath,围绕元素的稳定特征来写。
几个实用规则:
- 属性定位:
//button[@class='submit-btn'],比绝对路径短得多。 - 文本定位:
//span[contains(text(),'确认')],适合按钮文字经常变但关键字不变的场景。 - 组合定位:
//div[@class='modal']//button[contains(@class,'primary')],先圈范围再精确定位,比单个长表达式更抗改版。 - 避免使用下标:
//div[1]/div[2]/div[3]这种看着就脆,多一个节点整体失效。
XPath还有个容易踩的坑:属性值里的引号。如果属性值本身包含双引号,外层就用单引号,反过来一样。//button[contains(@title,'他说"确认"')]这种嵌套写法经常把人绕晕,我用的时候都先把属性值复制出来看一眼,确定引号类型再组装。
3.3 CSS Selector:性能好、写法短,建议和XPath双修
很多人只学XPath不学CSS,其实CSS Selector在Selenium里的表现非常优秀,语法也简单。#id是id选择器,.class是class选择器,[name='xxx']是属性选择器,div > p是父子关系。最关键的是:CSS Selector无法按文本内容定位,遇到“我想找文字是‘确认’的按钮”这种需求,CSS无能为力,必须用XPath,这是两者最大的能力分界线。
所以我的使用习惯是:能用CSS就用CSS,需要按文本定位或用复杂轴关系定位时切XPath。实践中这句话能解决大部分选择困难:
from selenium.webdriver.common.by import By # CSS方式 driver.find_element(By.CSS_SELECTOR, "button[data-action='submit']") # XPath方式 driver.find_element(By.XPATH, "//button[contains(text(),'提交')]")3.4 其他四种定位方式,什么场景才值得用
除了ID、XPath、CSS,Selenium还提供By.NAME、By.CLASS_NAME、By.TAG_NAME、By.LINK_TEXT和By.PARTIAL_LINK_TEXT。
By.NAME:表单元素常见,input、select的name属性经常是后端接口的字段名,稳定性尚可,适合表单页。By.CLASS_NAME:类名经常多个类叠加,比如class="btn btn-primary",用By.CLASS_NAME只匹配其中一个就行,但类名很容易被前端改样式时调换。By.TAG_NAME:只适合批量找标签,比如获取页面上所有img标签,正常定位不用它。By.LINK_TEXT:精确匹配链接文字;By.PARTIAL_LINK_TEXT是模糊匹配。只对<a>标签生效,适合翻页链接、导航菜单这类场景。
有个套路很好用:页面上一组结构相同的元素(比如多条数据列表),可以用find_elements配合索引或者遍历。这个方法我后面会专门讲。
3.5 八种定位方式对比速查
| 定位方式 | 语法示例 | 稳定性 | 性能 | 典型场景 |
|---|---|---|---|---|
| ID | By.ID | 极高 | 快 | 表单输入框、唯一按钮 |
| Name | By.NAME | 高 | 快 | 传统表单页 |
| Class Name | By.CLASS_NAME | 中 | 快 | 样式稳定的元素 |
| Tag Name | By.TAG_NAME | 低 | 快 | 批量取标签 |
| Link Text | By.LINK_TEXT | 中 | 中 | 导航链接、翻页 |
| Partial Link | By.PARTIAL_LINK_TEXT | 中 | 中 | 链接文字动态变化 |
| XPath | By.XPATH | 高 | 中慢 | 按文本、复杂关系 |
| CSS Selector | By.CSS_SELECTOR | 高 | 快 | 属性组合、层级结构 |
我的建议是:主修CSS和XPath两种,其他六种了解即可。定位能力不是背出来的,是用真实的页面反复调试练出来的。
4. 等待策略:精准定位的真正基石,比定位语法更重要
4.1 三种等待方式,很多人的失败都栽在这里
页面元素找不到,十个里有八个是时机问题:元素还没加载出来,代码就已经开始找了。Selenium提供三种等待,很多人混着装,实际上它们的天职完全不同。
- 强制等待
time.sleep(3):写死3秒,简单粗暴,但不管元素1秒就出来还是5秒才出来,都等3秒,慢且不可靠。 - 隐式等待
driver.implicitly_wait(10):设置一个全局的超时时间,每次find_element找不到元素时,会在这个时间内轮询等待。但它对“元素找到了但还没可点击、还没可见”这种状态毫无办法。 - 显式等待
WebDriverWait配合expected_conditions:针对某个元素、某种状态等待,既精准又高效,这才是自动化测试应该有的样子。
实际操作中,我不建议一上来就写显式等待,而是先判断页面用的是同步渲染还是异步渲染(Ajax)。同步页面加载完DOM就完整了,隐式等待基本够用;异步页面需要等接口返回后DOM才更新,必须显式等待。
4.2 显式等待的实战写法
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) # 等待元素可见并可点击 submit_btn = wait.until( EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'提交')]")) ) submit_btn.click()这里有个关键点:element_to_be_clickable比presence_of_element_located更严格,前者要求元素在DOM里且可见且可交互。对于按钮类操作我一般用前者;对于只需要读取文本的元素,用后者就够了,效率更高。
另一个进阶技巧:显式等待虽然好,但不要给每个元素都单独写一个WebDriverWait,那会让代码啰嗦到没法看。我习惯在测试工具类里封装一个方法:
def wait_and_click(driver, locator, timeout=10): element = WebDriverWait(driver, timeout).until( EC.element_to_be_clickable(locator) ) element.click() return element调用时传一个元组:wait_and_click(driver, (By.ID, "submit")),脚本清爽很多。
4.3 解决动态加载页面的定位难题
现代前端大量使用Vue、React这类框架,页面元素是异步渲染的,经常出现“元素存在但内容为空”“数据加载一半就返回了”的诡异情况。我处理这类页面的通用思路是三步:
- 先轮询等待数据接口对应的网络响应完成(用
driver.execute_script监听performance或者简单点,等某个loading图标消失)。 - 再等目标元素出现在DOM中。
- 最后再等元素状态变成可交互。
其中第二步和第三步就是上面讲的显式等待。第一步很多人忽略,但它能帮你规避掉大量“元素找到了,值还是空的”这种bug。如果你用Selenium 4,可以试试WebDriverWait的poll_frequency参数,默认0.5秒轮询一次,改成0.1秒能让定位更快,但会增加CPU开销,小项目无所谓,大项目要权衡。
4.4 等待策略的避坑清单
- 隐式等待和显式等待不建议混用,混用的结果是你明明设了10秒超时,实际可能等20秒,因为Selenium的等待机制会叠加。
- 不要对
find_elements用隐式等待的预期,它可能返回一个空列表而不是抛异常,你拿空列表再去遍历就会触发其他错误。 time.sleep不是不能用,而是在等待某个动态元素的同时你又没有更好的等待条件时,短sleep(0.5~1秒)作为兜底方案完全合理,但绝对不能全篇都是sleep。
5. 高效交互实战:从点击输入到JS操作父页面元素
5.1 最常用的交互:点击、输入、键盘、清空
点击和输入是两个最基本的操作,但用起来有不少细节。
# 输入前先清空,避免残留文本 element = driver.find_element(By.ID, "username") element.clear() element.send_keys("test_user") # 键盘操作 from selenium.webdriver.common.keys import Keys element.send_keys(Keys.CONTROL, "a") # 全选 element.send_keys(Keys.BACKSPACE) # 删除 element.send_keys(Keys.ENTER) # 回车 # 点击 driver.find_element(By.XPATH, "//button[text()='登录']").click()clear()的时机问题很多人忽略:如果输入框有默认值,直接send_keys会在默认值后面追加,导致数据不对。所以每次输入前先clear(),这是写自动化最容易被忽略的细节之一。
还有些前端的输入框是只读的,比如时间选择器,send_keys进不去。这时候套路是先用JS修改元素的readonly属性,再输入:
driver.execute_script("arguments[0].removeAttribute('readonly')", element) element.send_keys("2025-01-01")5.2 下拉框、复选框、单选框的正确打开方式
原生<select>标签的下拉框,用Selenium提供的Select类最方便:
from selenium.webdriver.support.ui import Select select_elem = Select(driver.find_element(By.NAME, "city")) select_elem.select_by_visible_text("北京") # 按显示文本选 select_elem.select_by_value("beijing") # 按value值选 select_elem.select_by_index(2) # 按下标选,从0开始但前端框架(比如Element UI、Ant Design)渲染的下拉框根本不是原生select,而是点击后出现一个浮层,里面是一堆li或div。这种不能直接套Select类,处理方式是:先点击触发下拉,再用显式等待浮层出现,然后从浮层里定位目标项。
复选框的勾选,除了直接click(),还有一种情况:元素本身被遮挡,点击报“other element would receive the click”。这时候用JS强制点击:
driver.execute_script("arguments[0].click();", checkbox_element)这个技巧适用面很广,后面讲常见问题还会提。
5.3 多表单切换与iframe里的父页面元素操作
嵌套页面是自动化里的隐形杀手。一个页面里嵌了iframe,你的find_element直接去找里面的元素,必然找不到,因为Selenium默认只在主文档里找。需要先切换进去,再操作,再切回来:
iframe = driver.find_element(By.TAG_NAME, "iframe") driver.switch_to.frame(iframe) # 在iframe里操作 driver.find_element(By.ID, "inner_input").send_keys("hello") # 操作完切回主文档 driver.switch_to.default_content()关于搜索词里那个parent.layer.getframeindex 操作父页面元素,这其实是layui框架里父子页面交互的典型问题。场景是:父页面打开一个iframe弹窗,弹窗里要操作父页面的某个元素。parent.layer.getframeindex()拿的是当前iframe在layer体系里的索引索引,配合它去定位父页面元素。
如果你在Selenium里遇到这种父子页面嵌套,正确解法不是用parent这个JS对象(那是浏览器窗口层面的事),而是用Selenium自己的frame切换。先switch_to.parent_frame()返回上一层,再定位父页面的元素,然后switch_to.frame(iframe)回到子页面继续操作。
# 假设当前在iframe中,切回父层 driver.switch_to.parent_frame() parent_btn = driver.find_element(By.ID, "parent_btn") parent_btn.click() # 再切回iframe driver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, "iframe[lay-id='myLayer']"))5.4 弹出框、Alert、文件上传这些特殊交互
原生浏览器的alert/confirm弹窗,不是DOM元素,用switch_to.alert处理:
alert = driver.switch_to.alert print(alert.text) # 获取弹窗文本 alert.accept() # 点确定 # alert.dismiss() # 点取消文件上传是另一个经典难关。如果页面上有<input type="file">,不需要真的去操作系统文件对话框,直接把文件路径发给输入框就行:
upload_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']") upload_input.send_keys("/path/to/myfile.pdf")但如果上传组件是自定义的,点击后调用底层文件选择窗口,Selenium就无能为力了。这时候我一般用pyautogui或者autoit这类操作系统层面的工具去配合输入文件路径,或者直接调用前端暴露的上传接口。
5.5 JS操作元素:定位之外的另一条腿
Selenium的execute_script能执行任意JavaScript,这在很多场景里比正规API还好用。搜索词里提到的“js数组操作元素”,在自动化里通常指用JS批量处理一组元素的场景。比如我想拿到页面上某列表的所有文本:
texts = driver.execute_script( "return Array.from(document.querySelectorAll('.item-list span')).map(el => el.textContent)" )这个写法用Array.from把NodeList转成数组,再map出文本,一次性拿到所有数据,比find_elements然后逐个取文本效率高不少。
还可以用JS做很多Selenium原生做不到的事:
- 拿到元素的任意属性:
arguments[0].getAttribute('data-id') - 改变元素样式做调试标记:
arguments[0].style.border='2px solid red' - 强制触发事件:
arguments[0].dispatchEvent(new Event('change')) - 直接操作Vue/React组件的内部状态(虽然不建议,但应急时很好用)
一个真实的例子:某后台管理系统用Vue写了一个自定义日期组件,点击后浮层渲染到body下面,Selenium怎么都定位不到。后来我用execute_script找到浮层里的输入框,直接赋值并触发input事件,问题秒解。JS操作元素这条腿一定要学会,因为它能绕过很多前端框架的坑。
5.6 用ActionsChains模拟鼠标键盘组合操作
鼠标悬停、拖动、双击、右键这些操作,用ActionsChains:
from selenium.webdriver.common.action_chains import ActionChains actions = ActionChains(driver) # 悬停到菜单上,再点击子菜单 menu = driver.find_element(By.ID, "nav-menu") child = driver.find_element(By.LINK_TEXT, "子菜单") actions.move_to_element(menu).click(child).perform()这里有个坑:ActionChains必须用perform()才会真正执行,很多人写完忘掉,然后一脸懵地找bug。还有个细节:执行完后最好sleep一下再走下一步,因为浏览器执行完操作到界面反应有一个微小的时间差,不等待容易出偶发失败。
拖拽用的drag_and_drop(source, target),在实现“把文件拖到上传区域”这类场景里经常遇到,注意目标区域一定要可见,否则拖拽无效。
5.7 element.click()被拦截时的高效兜底方案
正常交互里最常见的神秘报错:
selenium.common.exceptions.ElementClickInterceptedException: Element <button> is not clickable at point (x, y). Other element would receive the click翻译成人话:元素找到了,也可见,但页面上有个东西盖住了它。盖住它的通常是一个fixed定位的弹层、loading遮罩、或者广告浮层。兜底方案有几种:
- 等遮罩消失再点(显式等待它消失)。
- 用JS强制点击,无视遮挡(
:hover有时需要先hover上去)。 - 用
ActionChains先点击别处关闭浮层,再点目标元素。
我推荐优先用方案1,因为强制点击容易掩盖真实问题——比如按钮其实是因为某个校验没通过暂时不可用,你强制点了反而让后续依赖状态错乱。方案2适合页面有广告弹层这种与业务无关的干扰物,在爬虫场景里特别常用。
6. 常见问题与排查技巧实录
6.1 定位不到元素怎么排查,按这个顺序来
我把“元素找不到”的排查思路整理成一套固定流程,遇到问题照着走,比自己瞎试快得多:
| 排查步骤 | 操作 | 常见结论 |
|---|---|---|
| 1 | 确认页面是否加载完成 | 网络慢导致元素没出现 |
| 2 | 在DevTools中手动确认元素真实存在 | 可能xpath/css写错 |
| 3 | 检查元素是否在iframe内 | 需要切换frame |
| 4 | 检查元素是否为动态id/动态class | 需要换定位策略 |
| 5 | 检查是否被shadow DOM包裹 | 需要穿透shadow root |
| 6 | 看是否处于新打开的标签页/窗口 | 需要切换window handle |
第6条容易被忽略:driver.get打开的页面在标签页A,点击后浏览器新开标签页B,你还在A里面找B的元素,当然找不到。解法:
handles = driver.window_handles driver.switch_to.window(handles[-1]) # 切到最新标签页6.2 元素存在但get_attribute取不到值的几种情况
取文本或属性是断言功能是否正确的关键步骤。常见莫名其妙的情况是:element.text返回空字符串,但页面上明明有字。
原因通常是两组:一是元素本身用textContent渲染,而Selenium的.text属性只拿可见文本,需要改用get_attribute('textContent');二是数据是异步加载的,定位到的元素是空壳,文本还没填充进来,这时候加一个文本非空的等待条件:
WebDriverWait(driver, 10).until( lambda d: d.find_element(By.ID, "result").text.strip() != "" )6.3 跑批脚本时偶发失败的经典场景与对策
自动化脚本最大的痛点不是写不出来,而是“这次跑通过了,下次跑就挂”。偶发失败往往来自三大元凶:网络抖动、元素加载时序、浏览器内存膨胀。
对策思路也明确:
- 网络抖动:关键步骤加重试机制,失败重试1~2次。
- 加载时序:统一用显式等待,不信任sleep。
- 浏览器内存膨胀:跑一段时间后
driver.quit()重启浏览器,长任务切分批次。
这些问题的共性在于它们都不是代码逻辑错误,而是环境对抗问题。我的经验是:自动化脚本里预留容错逻辑,比追求一次跑对更重要,毕竟真实环境的不可控因素太多了。
6.4 Hook性能优化与并发执行建议
脚本稳定性解决之后,迟早会碰到速度问题。单线程跑几百个用例,体验很差。Selenium本身是阻塞式的,同时操作同一个浏览器的同一个页面没有意义,要做到并行,一般用concurrent.futures或者pytest-xdist开多个进程,每个进程起一个独立的driver。
这里有个注意点:多进程并行时,浏览器数量等于进程数,对机器内存要求很高。我一般限制在4~8个并行进程,再多反而会因为资源竞争导致失败率上升。另外,每个driver用完必须quit(),否则浏览器后台僵死占用资源,这是我踩过最深的坑。
还有一个小技巧:如果只是爬取数据而不需要渲染视觉效果,可以开启headless无头模式,速度能提升不少。但要注意,无头模式下某些前端特性可能不生效(比如UA标识不同导致服务端返回不同页面),上线前一定要先验证一遍。
6.5 可视化调试:把Selenium变成“直播”
搜索词里有“selenium爬虫可视化”,这个点确实很实用。我调试定位问题时的常规做法是:写脚本时给关键元素加高亮边框,然后截图留档。这样定位准不准一眼就能看出来。
def highlight(driver, element): driver.execute_script( "arguments[0].style.border='3px solid red'", element ) # 使用 ele = driver.find_element(By.ID, "username") highlight(driver, ele) driver.save_screenshot("debug.png")再加上driver.get_screenshot_as_png(),可以在断言失败时自动截图,方便回溯。对于复杂页面,我还会配合driver.page_source输出HTML快照,用于后续离线分析。这套组合拳在调试动态页面时简直救命。
7. 从入门到落地:一份可以抄作业的完整示例流程
说了这么多,最后给一套能直接跑的完整示例。场景:用Selenium打开一个带登录和列表页面的后台系统,输入账号密码,登录后定位列表数据,并把数据提取出来。
import time 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 # 初始化 driver = webdriver.Chrome() driver.implicitly_wait(5) try: driver.get("https://your-target-site.com/login") # 登录表单 username = driver.find_element(By.ID, "username") username.clear() username.send_keys("admin") password = driver.find_element(By.ID, "password") password.clear() password.send_keys("123456") password.send_keys(Keys.ENTER) # 等待登录跳转后,列表加载完成 wait = WebDriverWait(driver, 15) wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".data-row"))) # 提取所有数据行文本 rows = driver.find_elements(By.CSS_SELECTOR, ".data-row") for i, row in enumerate(rows[:5]): print(f"第{i+1}行: {row.text}") # 分页点击示例:等下一页按钮可点击再点 next_btn = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, "a.next-page")) ) next_btn.click() finally: driver.quit()这套流程里用了隐式等待做兜底、显式等待做核心依赖,两者分得清清楚楚。实际项目页面结构可能更复杂,但只要定位思路和等待策略到位,换成你的真实选择器就能直接复用。
再补充一个我常用的封装思路:把所有定位器(locator)放在一个配置文件里,脚本用变量引用。页面改版时只需要改配置,不用动业务代码。这在维护大型自动化项目时能省下大量时间。
最后再分享一个写自动化脚本的心法:别追求一次写好,先跑通再优化。第一版写得丑一点没关系,能跑起来你就能看到真实页面、真实时序,然后针对性地改。我见过太多人对着代码空想半天,都不愿意先跑一次,结果浪费的时间比写代码多十倍。Selenium这个东西,纸上谈兵永远学不会,打开浏览器、跑起来、把错误一个个解决掉,才是最快的成长路径。