尧图精选

Selenium driver 方法全解:从会话生命周期到元素操作与等待

🕒 发布时间:2026/10/1 15:06:54 📁 来源:尧图网络
写自动化脚本的第三年我才真正把 selenium 里的 driver 对象当回事。前面两年我只用它的四个方法get、find_element、click、quit遇到加载慢就time.sleep遇到点不动就加长等待。直到有一次接手一个后台管理系统的回归脚本页面里嵌套了 iframe、伪下拉框、动态渲染的表格靠sleep堆出来的脚本在 CI 上跑十次崩三次。那时候我才回头把 driver 上能调的方法一个个翻了一遍发现它总共挂载了六七十个方法我竟然只用了不到十分之一。这篇就把 selenium 中 driver 各类方法的实际用法、调用时机和踩坑点完整梳理一遍从会话生命周期的控制方法到窗口标签页、元素定位、元素级操作、等待超时、脚本注入、会话状态逐层拆开。不管你是刚学会webdriver.Chrome()的新手还是被StaleElementReferenceException折磨过的老手下面这些方法的真实语义和边界都值得再看一遍。1. driver 不是浏览器从会话对象到方法分类的心智模型绝大多数人对 driver 的误解是把它当成浏览器本身。你写driver webdriver.Chrome()脑子里想的是我打开了一个 Chrome但实际上你拿到的是一个会话代理对象。浏览器进程是它启动的但两者之间隔着一条 HTTP 通道。1.1 一次 webdriver.Chrome() 到底创建了什么执行这行代码时背后发生了四件事。第一Selenium 会去找一个和本地浏览器版本匹配的驱动可执行文件Selenium 4.6 之后由 Selenium Manager 自动处理不再需要手动配chromedriver路径这一条帮无数人省掉了版本对不上的报错。第二启动这个驱动进程它在本机监听一个随机端口。第三通过 HTTP 向POST /session发一个请求请求体里带着capabilities声明我想要什么样的浏览器、要不要无头模式、要不要禁用图片加载。第四服务端返回一个 JSON里面包含一个sessionId。这个sessionId才是 driver 的灵魂。你可以直接打印出来看from selenium import webdriver driver webdriver.Chrome() print(driver.session_id) # 类似 5f3a9c1e8b7d4a2f... print(driver.name) # chrome print(driver.capabilities) # 一大坨字典包含 browserVersion、platformName 等之后你调的每一个方法driver.get()、driver.find_element()、element.click()本质都是拿着 sessionId 往那个端口发一条符合 W3C WebDriver 规范的 HTTP 请求。理解这一点非常关键因为它解释了很多反直觉的现象为什么element.click()有时候会抛超时为什么脚本跑完不quit会残留一堆进程为什么跨机器跑 Grid 时截图会慢这些方法全都是远程调用不是本地函数。提示调试期可以打开--log-levelDEBUG观察每条请求和响应你会对一次 click 到底发了几个请求有全新的认识。1.2 driver 的方法其实只有六类我把 driver 上的方法按用途归成六类这个分类直接对应后面的章节结构也是我写脚本时的检索顺序类别代表方法主要用途会话与生命周期quitclosesession_idcapabilities创建、查询、销毁会话导航与窗口getbackforwardrefreshmaximize_windowswitch_to.window控制页面跳转和窗口布局元素查找find_elementfind_elements定位页面上的节点元素操作clicksend_keysclearget_attribute对定位到的节点做事等待与超时implicitly_waittimeouts.*控制等多久和等什么会话状态类get_cookiesswitch_to.frameswitch_to.alertsave_screenshotget_logcookie、框架、弹窗、截图、日志新手最容易忽略的是最后两类。前四类是干活的后两类是保命的。我见过太多脚本因为没处理 iframe 定位不到元素因为没关 cookie 导致第二个用例登录状态错乱因为没截图导致 CI 报错时完全不知道现场长什么样。1.3 为什么我不建议在脚本里硬编码 chromedriver 路径早年间大家都会写webdriver.Chrome(executable_path./chromedriver)然后每隔几个月浏览器自动升级一次脚本就集体挂掉报This version of ChromeDriver only supports Chrome version XX。现在executable_path参数已经废弃Selenium Manager 会根据本地浏览器版本自动下载匹配的驱动。如果你还在被这个问题困扰先升级 selenium 版本比手动维护驱动目录省事得多。2. 导航与窗口控制get、close、quit 背后的会话生命周期这一组方法看着最简单实际上出错率极高尤其是close和quit的区别我面试过的人里有一半答不上来。2.1 get 和导航三兄弟的关系driver.get(url)是最常用的方法它会等待页面文档加载完成受page_load_strategy影响。另外三个是driver.get(https://example.com/page1) driver.find_element(link text, 下一页).click() driver.back() # 等价于浏览器后退 driver.forward() # 前进 driver.refresh() # 刷新当前页back()和forward()依赖浏览器的历史栈如果你是通过execute_script打开的新页面历史栈里可能没有记录这时候back()会静默什么都不做。我在写流程类用例时会刻意避免依赖back()而是重新get()目标地址虽然慢一点但稳定得多。注意get()不是绝对等待。如果页面用了大量异步渲染get()返回时页面上可能还是一片空白。它只保证document.readyState达到interactive或complete不保证你的目标元素已渲染。2.2 close 与 quit一个关标签一个杀进程这是最经典的坑。两者的差别driver.close()关闭当前窗口标签页。如果当前只有一个窗口关闭后浏览器进程可能还在但sessionId已失效后续任何操作都会抛InvalidSessionIdException。driver.quit()结束整个会话关闭所有窗口杀掉驱动进程释放端口。结论很直接脚本结尾永远用quit()不要用close()。我习惯把它包进try/finallydriver webdriver.Chrome() try: driver.get(https://example.com) # 一堆操作 finally: driver.quit()只写quit()不写finally的脚本一旦中途断言失败抛异常浏览器就会一直挂着。跑几十个用例机器上堆几十个 Chrome 进程内存直接爆掉——这个场景我踩过不止一次排查了半天才发现是异常路径没有清理。2.3 窗口尺寸与位置的四个方法做响应式页面测试或截图对比时窗口尺寸必须固定driver.maximize_window() # 最大化 driver.minimize_window() # 最小化 driver.fullscreen_window() # 全屏F11 那种无地址栏 driver.set_window_size(1440, 900) # 指定宽高 driver.set_window_position(0, 0) # 指定左上角坐标 print(driver.get_window_size()) # {width: 1440, height: 900} print(driver.get_window_rect()) # 一次性拿 x/y/width/height有个细节maximize_window()在不同操作系统上的实际效果不一样Windows 上最大化后get_window_size()返回的是屏幕工作区尺寸Linux 无头模式下可能完全不生效。所以做视觉回归时我统一用set_window_size(1440, 900)显式指定而不是靠最大化。另外set_window_size是异步的调完之后建议加一个短暂的显式等待确认get_window_size()返回的值和你设置的一致再开始截图。2.4 多标签页切换handle 才是唯一标识点了target_blank的链接之后新标签页开了但 driver 的当前焦点还在旧标签上这时候find_element找的是旧页面的元素——这是新手最常见的元素明明在页面上却定位不到的原因。original driver.current_window_handle # 记住原窗口句柄 driver.find_element(css selector, a.open-new).click() # 等新窗口出现别用 sleep WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) 1) for handle in driver.window_handles: if handle ! original: driver.switch_to.window(handle) break print(driver.title) # 现在打印的是新标签页标题 driver.close() # 关掉新标签 driver.switch_to.window(original) # 切回原窗口Selenium 4 还提供了一个更省事的方法driver.switch_to.new_window(tab)或new_window(window)它会主动开一个新标签并自动切换过去不用再遍历window_handles。写在新页面里操作的用例时我基本都用这个。3. 元素定位方法find_element 返回的到底是什么定位方法是 selenium 里被写得最多、也被误解最多的部分。大部分教程只告诉你用 XPath 能找到元素但没人说清楚找到之后拿到的那个对象是什么。3.1 By 的八种策略以及我的选择优先级driver.find_element(By.ID, kw)和driver.find_element(id, kw)是等价的前者是 Selenium 4 推荐的写法。策略适用场景稳定性By.ID元素有唯一 id最高By.NAME表单元素高By.CSS_SELECTOR大多数场景的通用解高By.XPATH需要按文本、按层级关系定位中By.CLASS_NAMEclass 唯一的场景中By.TAG_NAME批量枚举同类标签中By.LINK_TEXT精确匹配链接文字低By.PARTIAL_LINK_TEXT模糊匹配链接文字低我的实际优先级是ID CSS Selector 相对 XPath 绝对 XPath。绝对 XPath 形如/html/body/div[3]/div/div[2]/ul/li[5]/a页面上多嵌一层 div 就废了除非实在没辙我不用。CSS Selector 里我最常用的几个写法driver.find_element(By.CSS_SELECTOR, input[placeholder请输入用户名]) driver.find_element(By.CSS_SELECTOR, table.list tbody tr:nth-child(2)) driver.find_element(By.CSS_SELECTOR, button[data-testidsubmit]) driver.find_elements(By.CSS_SELECTOR, .ant-table-row)># 不推荐异常做流程控制慢且难看 try: driver.find_element(By.ID, loading) print(还在加载) except NoSuchElementException: print(加载完了) # 推荐空列表判断干净利落 if driver.find_elements(By.ID, loading): print(还在加载)还有一个很多人不知道的事实find_elements找不到元素时不会等待隐式等待时间——不对这里要修正一下在配置了implicitly_wait的情况下find_elements同样会轮询到超时才返回空列表。所以如果你设了 10 秒隐式等待又在一个列表为空是正常状态的场景里调find_elements每次都会白等 10 秒。这时候把隐式等待设小一点或者干脆关掉隐式等待改用显式等待脚本速度会明显提升。3.3 仅存储定位元数据为什么元素对象像影子这一条是我认为最值得理解的设计。find_element返回的 WebElement 对象并不持有真实的 DOM 节点它内部只存了两样东西定位用的策略和值也就是定位元数据以及服务端返回的一个元素 ID。你后续每一次调element.click()、element.textselenium 都会重新发一条 HTTP 请求把元素 ID 传给驱动去执行。这解释了三件事。第一为什么element.text和element.get_attribute(innerText)结果可能不同——前者走的是 W3C 规范的GET /element/{id}/text接口有自己的一套可见性过滤规则。第二为什么页面刷新或局部重渲染之后之前拿到的元素对象会抛StaleElementReferenceException——元素 ID 已经在服务端失效了。第三为什么大量元素操作会让脚本变慢——每一次操作都是一次网络往返。应对StaleElementReferenceException的标准做法是不缓存元素对象随用随查或者把定位逻辑包成一个函数def get_username_input(): return driver.find_element(By.ID, username) # 每次调用都重新查一次天然规避 stale 问题 get_username_input().send_keys(tester)如果你确实需要缓存那就缓存定位器元组而不是缓存元素对象LOCATOR (By.ID, username) # 缓存这个 driver.find_element(*LOCATOR).send_keys(tester)3.4 页面元素枚举用 find_elements 把列表变成数据元素枚举这个说法听起来玄乎其实就是把一组元素转成可以遍历、可以断言、可以提取字段的结构。典型场景是抓表格rows driver.find_elements(By.CSS_SELECTOR, #result-table tbody tr) data [] for row in rows: cells row.find_elements(By.TAG_NAME, td) data.append([c.text for c in cells]) print(f共 {len(data)} 行) assert data[0][0] 张三注意row.find_elements是在元素对象上继续查找它会把搜索范围限制在这一行内部比在 driver 上写一大串层级 XPath 清晰得多。这个模式我在处理分页列表、菜单树、卡片流时反复用几乎是标准解法。3.5 原生下拉框和 divulli 伪下拉框这是实际项目里绕不开的一道坎。原生select用Select类from selenium.webdriver.support.ui import Select sel Select(driver.find_element(By.ID, city)) sel.select_by_index(2) sel.select_by_value(sh) sel.select_by_visible_text(上海) print([o.text for o in sel.options]) # 枚举所有选项 print(sel.first_selected_option.text) # 当前选中项但现在的组件库Ant Design、Element UI 等几乎都用divulli组合来模拟下拉框Select类对它完全无效会直接抛UnexpectedTagNameException。判断方法很简单看 DOM 里有没有select标签。对伪下拉框正确姿势是点击触发器 → 等待选项列表可见 → 按文本点击 litrigger driver.find_element(By.CSS_SELECTOR, .city-select__trigger) trigger.click() # 关键等选项容器可见而不是等某个固定时间 option_list WebDriverWait(driver, 10).until( EC.visibility_of_element_located((By.CSS_SELECTOR, ul.city-options)) ) for li in option_list.find_elements(By.TAG_NAME, li): if li.text.strip() 上海: li.click() break坑在于这类组件的ul往往一直存在于 DOM 中只是靠 CSS 的display: none或opacity: 0隐藏。所以你要用visibility_of_element_located而不是presence_of_element_located否则会拿到一个隐藏的列表点上去直接报ElementNotInteractableException。另外选项数量多时列表会虚拟滚动只有可见的 li 才真实存在这种情况下需要先滚动容器再定位。4. 元素级操作方法click 被拦截的七种原因与对策定位到元素只是拿到钥匙真正开锁的是元素操作类方法。这一组方法在 selenium 4 里有一些值得注意的语义差异。4.1 send_keys 和 clear 的顺序陷阱send_keys是追加输入不是覆盖。输入框里默认有内容时直接send_keys会变成拼接driver.find_element(By.ID, kw).send_keys(hello) # 输入框已有 world # 结果是 worldhello正确写法是先清空再输入el driver.find_element(By.ID, kw) el.clear() el.send_keys(hello)但clear()也不是万能的。React 或 Vue 的受控表单里clear()触发的是原生事件某些框架不会同步更新内部状态导致界面上看着空了、提交时还是旧值。我遇到过最稳的方案是组合键全选 删除from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains el.click() ActionChains(driver).key_down(Keys.CONTROL).send_keys(a).key_up(Keys.CONTROL).perform() el.send_keys(Keys.DELETE) el.send_keys(hello)Mac 上要把CONTROL换成COMMAND。这个方案我在三个不同的前端框架项目里验证过比clear()可靠。4.2 is_displayed、is_enabled、is_selected 的真实语义这三个is_开头的方法经常被误用is_displayed()元素是否可见。注意它是递归判断的——如果父元素display: none子元素即使自身样式正常也返回 False。元素在屏幕外需要滚动才能看到依然算可见。is_enabled()对应 HTML 的disabled属性。按钮被置灰时返回 False。但很多组件库用 CSSpointer-events: none来禁用is_enabled()检测不到只能靠get_attribute(class)判断。is_selected()只对 checkbox、radio、option 有效判断的是原生选中状态。自定义样式的开关组件DOM 里可能是个 divis_selected()永远返回 False得看aria-checked属性。我写断言时的习惯是先is_displayed()再操作但不要用if is_displayed(): click()这种写法当作等待——它不等待只是立刻返回当前状态。等待必须交给显式等待处理。4.3 get_attribute 和 get_property 差在哪这是 Selenium 4 新增的区分非常实用el.get_attribute(value) # 拿 HTML 属性值反映初始值 el.get_property(value) # 拿 JS 属性值反映当前实时值 el.get_dom_attribute(value) # 只查 DOM 上写的那个属性 el.get_attribute(innerHTML) # 常用拿元素内部 HTML el.get_attribute(href) # 拿绝对 URL比 .text 更适合做断言输入框里敲了字之后get_attribute(value)可能返回空或旧值而get_property(value)返回当前输入的内容。我踩过这个坑断言输入成功却一直失败查了半天发现用错了方法。判断用户输入后的值用get_property判断元素初始配置用get_attribute。还有一个隐性差异get_attribute在元素不存在某个属性时返回None而get_dom_attribute同样返回None但get_property可能返回空字符串。写断言时记得兼容这两种情况。4.4 text 取不到值的五种情况element.text返回的是渲染后可见文本以下情况会返回空字符串元素被display: none隐藏。元素被滚动到视口外且浏览器优化了渲染少见但存在。文本在子元素的伪元素::before/::after里。文本在input的 value 里而不是文本节点。用了visibility: hidden或opacity: 0。这时候改用el.get_attribute(textContent)就能拿到因为它绕过可见性判断直接返回 DOM 里的文本内容。区别是它会包含隐藏的后代文本做精确断言时要先strip()并注意换行符。4.5 click 被拦截的常见原因排查表ElementClickInterceptedException和ElementNotInteractableException是元素操作里最常见的两个异常。把它们的原因理清楚能省下大量瞎调等待的时间现象根本原因处理方式ElementClickInterceptedException元素被弹窗、遮罩、吸顶导航挡住先关掉遮挡物或用 JS 点击ElementNotInteractableException元素不可见、宽高为 0等可见后操作或滚动到视口ElementClickIntercepted偶发动画还没结束就点击等element_to_be_clickable点击无反应但不报错组件监听的是 mousedown/mouseup 而非 click用 ActionChains 模拟完整点击只能点中边缘元素被 sticky 头部覆盖一部分execute_script滚动留出空间兜底方案是 JS 点击driver.execute_script(arguments[0].click();, el)。但我强烈建议把 JS 点击当最后手段因为它绕过了真实的用户交互路径可能触发不了前端框架的事件绑定导致脚本显示成功、实际业务没生效。我给自己定的规矩是先修等待再修滚动最后才用 JS。5. 等待、超时、脚本注入driver 的三个兜底能力如果只能给新手提一条建议那就是把sleep全部替换掉。而替换的工具全都挂在 driver 上。5.1 implicitly_wait 的全局污染问题driver.implicitly_wait(10)只写一行看起来性价比极高。但它有两个副作用需要知道第一它是全局的对整个 driver 生命周期内所有find_element生效你没法给某个定位单独设置。第二它和显式等待混用时会叠加——显式等待 10 秒、隐式等待 10 秒某些场景下总耗时可能达到 20 秒。我的实际选择是不用隐式等待全部用显式等待。理由是可预测。隐式等待会让元素不存在这个正常分支也变得很慢而显式等待精确到每一处。如果非要用把值设在 3 到 5 秒之间别设 30 秒。5.2 WebDriverWait 和 expected_conditions 的正确组合显式等待的标准结构是三段等待器 条件 超时。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, 15, poll_frequency0.5, ignored_exceptions[NoSuchElementException]) el wait.until(EC.element_to_be_clickable((By.ID, submit))) el.click() # 等元素消失常用于等 loading 遮罩 wait.until(EC.invisibility_of_element_located((By.CLASS_NAME, loading-mask))) # 等文本出现用于等异步结果 wait.until(EC.text_to_be_present_in_element((By.ID, status), 完成))poll_frequency默认 0.5 秒意思是每 0.5 秒查一次。把它调小到 0.2 秒能让脚本更快响应但会增加请求量。我在本地调试时调到 0.2跑 CI 时保持默认。比element_to_be_clickable更精确的一个技巧是等元素稳定——有些元素在动画期间位置一直在变clickable会在动画中途返回 True点击落空。这时候可以自定义条件比较两次element.rect是否一致def stable_element(locator): def _predicate(d): el d.find_element(*locator) rect1 el.rect time.sleep(0.2) return el if el.rect rect1 else False return _predicate WebDriverWait(driver, 10).until(stable_element((By.ID, floating-btn)))这段代码我在做拖拽和动画密集的页面时救过场分享给同样被偶发点击失效折磨的人。5.3 driver.timeouts四个超时各管什么除了隐式等待Selenium 4 把超时拆成了独立的属性driver.timeouts.implicit_wait 5 # 元素查找等待 driver.timeouts.page_load 30 # 页面加载超时 driver.timeouts.script 15 # execute_script 执行超时page_load特别有用。默认值是 300 秒意思是如果页面里有个永远加载不完的第三方统计脚本driver.get()会卡五分钟。我通常设成 30 秒超时后抛TimeoutException——但注意抛异常不代表页面不能用很多情况下页面主体已经渲染完了只是某个资源没加载完。这时候可以捕获异常继续往下走from selenium.common.exceptions import TimeoutException driver.timeouts.page_load 30 try: driver.get(long_loading_url) except TimeoutException: driver.execute_script(window.stop();) # 主动停止加载window.stop()是我常用的一个小技巧等价于浏览器上的停止加载按钮能立刻中断那些拖后腿的请求让脚本继续跑。5.4 execute_script 与 execute_async_scriptexecute_script是 driver 上的逃生舱同步执行 JS 并返回结果# 拿页面信息 title driver.execute_script(return document.title;) scroll_height driver.execute_script(return document.body.scrollHeight;) # 滚动到指定位置或元素 driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) driver.execute_script(arguments[0].scrollIntoView({block:center});, el) # 改元素属性用于处理只读输入框或隐藏文件上传 driver.execute_script(arguments[0].removeAttribute(readonly);, el) driver.execute_script(arguments[0].style.displayblock;, hidden_upload)execute_async_script用于等异步 JS 回调完成它要求脚本最后调用arguments[arguments.length - 1]传回结果result driver.execute_async_script( const done arguments[arguments.length - 1]; setTimeout(() done(window.localStorage.getItem(token)), 1000); )这个能力在做等某个前端全局变量变成期望值时特别顺手比轮询 DOM 更直接。6. 会话状态类方法cookie、frame、弹窗、截图、日志最后一组方法平时不出现在教程里但每一个都是线上脚本稳定性的关键。6.1 cookie 三件套与必须先访问域名driver.add_cookie({name: token, value: abc123}) print(driver.get_cookies()) driver.delete_cookie(token) driver.delete_all_cookies()最常见的坑是在driver.get()之前调add_cookie会报InvalidCookieDomainException因为当前页面还是data:,cookie 没有归属域名。必须先打开目标站点的任意页面再加 cookie然后刷新让 cookie 生效。driver.get(https://example.com/login) driver.add_cookie({name: token, value: token_from_api}) driver.refresh() # 关键刷新后服务端才读到新 cookie print(driver.current_url) # 应该已经在登录后的页面这个接口登录 注入 cookie 刷新的模式是我做自动化登录绕验证码时最常用的方案。它比 UI 走登录流程快得多而且稳定。要注意 cookie 的domain和path要对得上跨子域时要显式指定domain: .example.com前面带点表示包含子域。6.2 frame 切换的三个入口页面里嵌了 iframe 时driver 的查找上下文必须切进去driver.switch_to.frame(0) # 按索引最不稳 driver.switch_to.frame(frame-name) # 按 name 或 id 属性 driver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, iframe.pay)) # 按元素最推荐 # 操作完必须切出来 driver.switch_to.default_content() # 回到最外层 driver.switch_to.parent_frame() # 回到上一层多层嵌套时用坑点在于switch_to.frame之后find_element只在那个 frame 内部查找原页面上的元素全都找不到了。我见过有人切进 frame 操作完忘了切出来后面所有定位全失败排查了一下午。建议用上下文管理器把这件事封装起来from contextlib import contextmanager contextmanager def in_frame(driver, locator): frame WebDriverWait(driver, 10).until( EC.presence_of_element_located(locator)) driver.switch_to.frame(frame) try: yield finally: driver.switch_to.default_content() with in_frame(driver, (By.CSS_SELECTOR, iframe.pay)): driver.find_element(By.ID, card-number).send_keys(6222...)6.3 alert、confirm、prompt 的处理原生弹窗不属于 DOM只能用switch_to.alert处理alert driver.switch_to.alert print(alert.text) # 读弹窗文字 alert.accept() # 点确定 alert.dismiss() # 点取消 # prompt 才需要 send_keys driver.switch_to.alert.send_keys(输入内容) driver.switch_to.alert.accept()关键问题是时机alert 弹出的瞬间后续所有 driver 操作都会被浏览器的模态弹窗阻塞。如果你在弹窗弹出之后才去找元素会一直等到超时。所以处理顺序应该是触发动作 → 立刻处理弹窗 → 再继续。另外driver.switch_to.alert在没有弹窗时会直接抛NoAlertPresentException建议配合显式等待WebDriverWait(driver, 5).until(EC.alert_is_present())注意页面自己用 div 模拟的弹窗不是真 alertswitch_to.alert对它无效要老老实实用元素定位去点它的关闭按钮。6.4 截图的四种方法及适用场景driver.save_screenshot(debug.png) # 保存到文件返回 True/False driver.get_screenshot_as_file(debug.png) # 同上返回 Bool png_bytes driver.get_screenshot_as_png() # 返回 bytes可上传到报告系统 b64 driver.get_screenshot_as_base64() # 返回 base64适合嵌入 HTML 报告我强烈建议在pytest的失败钩子里自动截图def pytest_runtest_makereport(item, call): if call.when call and call.excinfo is not None: driver item.funcargs.get(driver) if driver: name fscreenshots/{item.name}_fail.png driver.save_screenshot(name)顺带说一句save_screenshot只截视口可见区域。想要整页截图得用 CDP 或者先调整窗口高度到document.body.scrollHeight再截。Chrome 下可以用driver.execute_cdp_cmd(Emulation.setDeviceMetricsOverride, { width: 1440, height: driver.execute_script(return document.body.scrollHeight), deviceScaleFactor: 1, mobile: False }) driver.save_screenshot(full_page.png)截完记得用Emulation.clearDeviceMetricsOverride恢复否则后续截图都会被这个尺寸覆盖。6.5 日志、能力查询和几个冷门但好用的方法driver.get_log(browser) # 浏览器控制台日志排查 JS 报错神器 driver.log_types # [browser, driver, performance] 视浏览器而定 driver.capabilities # 会话能力确认无头模式、浏览器版本 driver.current_url # 当前 URL断言跳转最常用 driver.title # 当前标题 driver.page_source # 完整 HTML配合 BeautifulSoup 做二次解析get_log(browser)需要启动时开启日志能力。它能让你在前端抛 JS 错误时立刻知道而不是等到元素找不到才回头怀疑。我在做支付流程测试时就是靠它发现了一个被try/catch吞掉的接口报错。还有一个冷门方法driver.print_page()Chrome 无头模式下把当前页导出成 PDF 的 base64做留证或报表归档很方便——前提是开了无头模式有头模式下可能会弹打印对话框。把上面这些方法串起来其实 driver 的使用逻辑就清晰了会话方法管生死窗口方法管焦点查找方法管上下文操作方法管交互等待方法管节奏状态方法管现场。我自己的脚本里这六类方法的调用比重大概是 1:2:20:15:8:3查找和操作占了绝大多数但真正决定脚本稳不稳的恰恰是那几行容易被忽略的等待和状态处理。下次再遇到元素找不到或者点了没反应先别急着调sleep按这个顺序过一遍焦点在不在对的窗口、上下文在不在对的 frame、等待条件是不是真的匹配元素状态、元素对象是不是已经失效。九成的偶发失败答案都在这四个问题里。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →