首页 / 资讯中心 / 文章详情

Selenium自动化测试:窗口尺寸、截图与刷新页面操作详解

Selenium自动化测试:窗口尺寸、截图与刷新页面操作详解 ★ FEATURED ARTICLE
做自动化测试的朋友应该都会遇到这种情况脚本跑着跑着窗口尺寸没按预期打开、截图内容跟期望对不上、页面数据刷新不及时排查半天才发现是几个最基础API的用法没吃透。这篇文章我把Selenium里三个最常用、也最容易埋坑的操作——浏览器尺寸设置、截图、刷新页面——从头到尾拆一遍从API用法到背后的执行逻辑再到实际项目里怎么组合用一次说清楚。这篇文章适合刚接触自动化测试、准备把Selenium用出生产力的新手也适合已经在写脚本但总觉得哪里不对的进阶选手。三个功能单独看都很简单但组合在一起就是一套完整页面巡检、回归验证、视觉比对的最小闭环。看完你手里的脚本稳定性至少能上一个台阶。1. 项目概述三个小功能背后的自动化思路1.1 这三个操作解决的是什么问题先说窗口尺寸设置。很多人以为它只是把浏览器调大调小的装饰性操作实际完全不是。尺寸设置直接决定了页面以什么视口宽度渲染进而影响CSS媒体查询命中哪一套样式、元素是否被折叠进汉堡菜单、弹窗定位是否偏移。一句话窗口尺寸决定了自动化脚本看到的页面长什么样这一步错了后面所有断言和截图都是错的基础。再说截图。自动化测试里截图是眼见为实的证据链用例失败了要截图定位视觉回归要截图比对页面挂没挂要截图确认。Selenium的截图API能保存当前窗口可见区域进阶用法还能截整个页面、截某个元素这几种截图方式对应完全不同的断言需求混着用容易出问题。最后是刷新。刷新看似只是调一下refresh()但它牵涉到一个核心问题页面重载之后之前拿到的所有元素引用全部失效脚本里如果没处理好这个状态变化下一秒就是StaleElementReferenceException糊脸。刷新真正要解决的是数据变了、我要重新获取页面状态这个需求怎么刷、刷完怎么等比刷新本身更值得研究。1.2 这三个功能组合起来能做什么把三者串起来就是一个典型的自动化巡检任务设置一个标准视口尺寸打开目标页面等待渲染完成截图留档刷新页面观察数据变化再截一张图做前后对比。不需要多复杂的框架就这五个步骤已经能覆盖大量日常回归和页面监控需求。我见过一个很典型的例子某运营后台的列表页每天凌晨会上新一批数据开发写的分页逻辑偶尔会把新品放到第二页人工盯不过来了于是写了个Selenium脚本——每次刷新后截图再用像素差对比前一天的基准图超过阈值就报警。整个脚本核心就依赖这三个API稳定跑了几个月给团队省了不知道多少人工盯盘的时间。1.3 为什么选Selenium而不是别的工具现在浏览器自动化工具不少Playwright和Puppeteer热度也很高。但Selenium依然是很多人第一选择原因很实际生态成熟、资料多、能兼容几乎任何浏览器和语言环境而且很多遗留项目的自动化代码就是Selenium写的接手之后你绕不开它。另外Selenium走的是WebDriver标准协议对浏览器原生事件的支持比较彻底不需要像一些轻量工具那样依赖额外的运行时。选择Selenium还有一个隐藏优势它的API设计足够薄。尺寸、截图、刷新这些操作都是直白的接口反而让学习者更容易理解浏览器自动化背后的原理。等你把Selenium这些基础吃透了再去上手Playwright那套更现代的API会发现概念是通用的只是换了一层皮。2. 环境准备先把Selenium正确跑起来2.1 安装依赖与浏览器驱动这一步看似简单但我在实际带项目时发现至少一半的脚本跑不起来问题都出在环境环节。Selenium本体的安装很简单pip install selenium真正麻烦的是浏览器驱动。Selenium不会自带浏览器它需要通过一个翻译官——WebDriver——来跟浏览器通信。比如你用Chrome就得有对应版本的chromedriver。驱动版本必须跟浏览器主版本一致否则启动时会直接抛SessionNotCreatedException。from selenium import webdriver from selenium.webdriver.chrome.service import Service service Service(executable_path/path/to/chromedriver) driver webdriver.Chrome(serviceservice) driver.get(https://example.com)这段代码是最朴素的写法executable_path指向你下载的驱动文件路径。很多教程到这里就结束了但真正项目里我更推荐用webdriver-manager这个辅助库来免去手工管理驱动的痛苦pip install webdriver-managerfrom selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)它会自动检测你本机Chrome的版本然后去下载匹配的驱动省掉了每次浏览器升级后驱动失效的麻烦。2.2 驱动版本匹配的三个关键点第一浏览器和驱动的主版本号必须一致。Chrome 130配chromedriver 130没问题拿128的驱动去配130的浏览器大概率直接启动失败。小版本不一致通常问题不大但主版本差一个数字就是天壤之别。第二驱动的下载路径要保证可访问。不管是用webdriver-manager还是手工下载都要确认驱动文件有执行权限特别是Linux环境下否则会报cannot find Chrome binary这类让人摸不着头脑的错误。这不是找不到浏览器而是驱动文件本身没执行权限。第三环境变量的设置。手工管理驱动时可以把驱动所在目录加到PATH里这样代码里就不用写Service参数了driver webdriver.Chrome()Selenium会自己去PATH里找驱动。这一步配置好了换机器部署时能少改很多代码。2.3 用最小脚本验证环境环境装没装好别急着写业务逻辑先跑一个三行脚本验证from selenium import webdriver driver webdriver.Chrome() driver.get(https://example.com) print(driver.title) driver.quit()能正常打印出页面的title内容说明驱动、浏览器、Selenium三者通信正常。看到这句输出你的环境就算是立住了。注意最后一定要写driver.quit()不是driver.close()。close()只关闭当前标签页进程可能还在后台挂着quit()才是彻底释放驱动和浏览器进程。这个细节平时看不出来跑多了就能感受到进程残留带来的资源泄漏问题。3. 窗口尺寸设置一个API背后藏着布局验证逻辑3.1 尺寸API全家桶Selenium提供的窗口控制API比很多人以为的多除了最常用的set_window_size还有一整套相关操作from selenium import webdriver driver webdriver.Chrome() # 设置指定宽高单位是像素 driver.set_window_size(1280, 800) # 获取当前窗口尺寸 size driver.get_window_size() print(size[width], size[height]) # 最大化窗口 driver.maximize_window() # 最小化窗口 driver.minimize_window() # 全屏模式F11效果 driver.fullscreen_window() # 设置窗口位置 driver.set_window_position(100, 50)set_window_size(1280, 800)是最核心的调用两个参数分别是宽度和高度单位是CSS像素。这里要记住一个概念窗口尺寸不等于视口尺寸。窗口包含了浏览器边框、地址栏、标签栏这些装饰真正渲染页面的视口会比窗口小一些。Selenium里set_window_size设置的是整个浏览器窗口的外部尺寸不过在实际项目中这个差异通常不构成问题只要你的目标是在标准尺寸下观察页面设置外部窗口尺寸就够了。3.2 尺寸背后真正的价值响应式布局验证这才是窗口尺寸设置的重头戏。现在几乎所有网页都做了响应式适配同一个URL在不同宽度下渲染成完全不同的布局。桌面端有的内容移动端可能收进侧边栏或者汉堡菜单里移动端排版正常的内容桌面端可能出现布局错位。自动化测试里窗口尺寸直接决定了你要验证哪一套布局。你拿一个默认的浏览器窗口去测移动端页面测出来的结果是没有任何参考意义的。所以正经的自动化项目里窗口尺寸往往是一组固定参数写在配置里viewports { mobile: (375, 812), tablet: (768, 1024), desktop: (1920, 1080), }然后针对每个尺寸各跑一轮用例检查关键元素在不同视口宽度下是否可见、位置是否正确。这就是响应式布局回归测试的基本盘。顺便说一句mobile的375宽度是iPhone SE到iPhone 12这一系列逻辑宽度的主流基准812是配套的逻辑高度这套组合基本能覆盖主流小屏场景。3.3 窗口尺寸与后续操作的联动关系尺寸设置最好在driver.get()之前完成或者至少要在页面关键交互开始之前设置好。因为页面一旦加载完成布局就已经按当前视口渲染了你再去改窗口尺寸页面会重新布局但部分依赖初始布局计算出来的元素坐标可能没有完全更新极端情况下会出现点击位置偏移。我自己的习惯是这样的顺序driver.set_window_size(1280, 800) driver.get(https://example.com)先定窗口再开页面确保渲染从一开始就是按目标视口来的。这个顺序在头几次写脚本的时候很容易忽略等发现元素定位偶尔飘忽不定回头排查才发现是尺寸设置晚了一步。还有一点值得提如果脚本里要对同一页面做多尺寸遍历或者用window.resizeTo这类JS操作去改视口需要及时重新计算元素状态。最好的做法是改完尺寸后执行一次页面刷新让布局完全重排再进行后续断言。这就涉及到后面第5节的刷新操作了三者的联动关系在这里已经初步显现。3.4 headless模式下的尺寸坑无头浏览器headless环境下窗口尺寸设置的坑更隐蔽。看这段代码options webdriver.ChromeOptions() options.add_argument(--headlessnew) options.add_argument(--window-size1920,1080) driver webdriver.Chrome(optionsoptions)--window-size这个启动参数必须写在add_argument里因为在无头模式下没有可见窗口set_window_size虽然也能调用但某些特殊场景比如依赖页面innerHeight判断逻辑的脚本可能拿到的还是默认尺寸。稳妥的做法是启动参数里显式设置--window-size代码里再调用一次set_window_size做兜底。另外无头模式截图时如果页面长度超过屏幕高度直接截图只会截到视口那部分。很多人在这个坑里栽过跟头本地有头模式跑得好好的一换到服务器上的无头模式截图面积直接少了一大截。解决方案要么用第4节提到的全页面截图方案要么把窗口高度动态设置为页面高度后再截图。4. 截图功能从基础API到生产级方案4.1 三种截图API对比Selenium的截图接口不止一个很多人只用了其中一个遇到问题才发现自己选错了。API返回内容典型用途driver.save_screenshot(path)直接保存PNG文件到指定路径简单场景留存证据driver.get_screenshot_as_base64()Base64字符串需要传输、入库、或二次处理时driver.get_screenshot_as_png()PNG二进制字节内存中处理图片不落盘三种方式截出来的内容本质上都是当前视口的可见区域。区别只在于你拿到的是文件、Base64字符串还是字节流。# 方式一直接保存文件 driver.save_screenshot(home.png) # 方式二Base64字符串 import base64 img_data driver.get_screenshot_as_base64() with open(home_bs64.png, wb) as f: f.write(base64.b64decode(img_data)) # 方式三PNG字节 png_bytes driver.get_screenshot_as_png() with open(home_bytes.png, wb) as f: f.write(png_bytes)个人建议日常调试用save_screenshot最省事一行代码搞定但如果要把截图发到消息通知、上传到对象存储、或者跟历史截图做像素对比get_screenshot_as_base64更灵活因为字符串好拼接好传输。4.2 全页面长截图的三步方案默认截图只截视口想要截完整页面网上常见的一种土办法是先把窗口拉高再截图page_height driver.execute_script(return document.documentElement.scrollHeight) page_width driver.execute_script(return document.documentElement.scrollWidth) driver.set_window_size(page_width, page_height) driver.save_screenshot(full_page_tmp.png)思路很简单把窗口高度临时改成页面总高度让视口能覆盖整个页面然后截图。这个办法在多数静态页面下是可用的但有副作用改变了窗口尺寸而且遇上页面内部有懒加载图片时高度计算可能不准截出来底部是空的。更干净的方案是走Chrome DevTools Protocol直接让浏览器截取超出视口的画面from selenium import webdriver driver webdriver.Chrome() driver.get(https://example.com) # 通过CDP开启完整页面捕获模式 driver.execute_cdp_cmd(Emulation.setDefaultBackgroundColorOverride, {color: {r: 255, g: 255, b: 255, a: 255}}) params { format: png, captureBeyondViewport: True, fromSurface: True, } result driver.execute_cdp_cmd(Page.captureScreenshot, params) import base64 with open(full_page_cdp.png, wb) as f: f.write(base64.b64decode(result[data]))captureBeyondViewport: True是核心开关它让浏览器无视视口边界把整个可滚动区域都画出来。这个方法不改变窗口尺寸也不会因为懒加载导致漏截稳定性比拉高窗口方案强不少。注意execute_cdp_cmd属于Chrome专属API如果项目必须兼容Firefox还是得用回拉窗口的方式。4.3 元素级截图只截关键区域有些场景不需要整页截图只想截某个按钮、某张图或者某个数据卡片用元素截图API正合适from selenium.webdriver.common.by import By element driver.find_element(By.ID, price-card) element.screenshot(price_card.png)元素截图会自动计算元素的位置和尺寸然后截取对应区域。它的好处是结果干净不会被页面上其他动态内容干扰适合做视觉回归比对。比如你只关心价格数字有没有变化那就只截价格区域比截整页再切割要省事得多。元素截图也有一个坑如果元素在视口外早期版本的驱动会截不到内容。稳妥做法是截图前先用element.location_once_scrolled_into_view把元素滚动到可视区域或者干脆先调一下execute_script(arguments[0].scrollIntoView(), element)。4.4 截图文件管理与命名规范截图功能本身不难但文件管理做不好截图再多也是一堆垃圾。我见过不止一个项目跑完一轮测试生成了几百张1.png、2.png这样的文件根本没法对应到具体用例复盘全靠猜。后来我定的规范是这样import time from pathlib import Path def save_screenshot(driver, case_name): ts time.strftime(%Y%m%d_%H%M%S) save_dir Path(./screenshots) / time.strftime(%Y%m%d) save_dir.mkdir(parentsTrue, exist_okTrue) path save_dir / f{case_name}_{ts}.png driver.save_screenshot(str(path)) return str(path)按日期文件夹 用例名_时间戳的结构组织截图既能按时间维度追溯又能按用例维度筛选。实测下来这种命名方式在排查问题时效率极高打开文件夹一眼就能找到对应时间点和用例的截图。截图质量方面还有几个容易忽略的点截图前先确认页面加载完成没加载完就截图很容易截到白屏或半成品需要做像素比对时尽量固定窗口尺寸否则同一页面在不同尺寸下截出来的图根本没有可比性生产环境建议加上重试机制截图失败时等待片刻再试一次。5. 页面刷新不只是refresh()这一个调用5.1 三种刷新方式分别适用什么场景Selenium里触发页面刷新至少有三种写法效果和使用场景各不相同# 方式一WebDriver提供的刷新方法 driver.refresh() # 方式二重新加载当前URL driver.get(driver.current_url) # 方式三通过JavaScript刷新 driver.execute_script(location.reload())driver.refresh()是最官方、最推荐的方式。它触发的行为等同于用户在浏览器里点刷新按钮会重新请求当前URL并重新渲染页面页面加载事件、资源请求这些流程都会正常走一遍。driver.get(driver.current_url)本质上是一次新的导航效果跟refresh()接近但多了一步URL解析。它的一个隐藏用途是清除URL中的锚点#后面的部分如果你之前通过脚本跳到了页面内某个锚点位置用refresh()可能会留在锚点处而重新get当前URL可以回到页面顶部。execute_script(location.reload())是JavaScript层面的刷新绕过WebDriver的导航机制。它适合一些特殊场景——比如某些页面监听了beforeunload事件WebDriver的refresh()可能会被拦截弹窗卡住而直接执行location.reload()可以规避这个交互。但要注意JS刷新的reload()默认带参数false表示从缓存加载如果页面改了静态资源可能拿不到最新版。5.2 刷新之后必须处理的元素失效问题这是全节最重要的一点。页面刷新后DOM树全部重建之前用find_element拿到的所有WebElement对象全部变为失效引用这时候再对它们做任何操作立刻抛StaleElementReferenceException。# 这段代码一定会报错 driver.get(https://example.com) btn driver.find_element(By.ID, submit) driver.refresh() btn.click() # StaleElementReferenceException正确的做法是刷新后重新定位元素。这个坑几乎每个写自动化的人都踩过处理方式说白了就是旧元素别留恋重新找一遍。实际项目中我更推荐把元素定位和业务操作封装成函数def get_submit_btn(driver): return driver.find_element(By.ID, submit) driver.get(https://example.com) get_submit_btn(driver).click() driver.refresh() get_submit_btn(driver).click() # 重新定位安然无恙这样每次操作都重新走一遍定位就算页面刷新过也不会踩到失效引用。5.3 刷新和等待必须成对出现刷新是异步操作driver.refresh()返回时页面未必加载完成尤其是那些包含大量异步请求的SPA页面。刷新后立刻去定位元素大概率会扑空。这时候需要把显式等待拉进来from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By driver.refresh() WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, data-table)) )presence_of_element_located只是检查元素出现在DOM里如果还要确保元素可见可点用visibility_of_element_located或element_to_be_clickable更稳。这套刷新 显式等待的组合是自动化脚本稳定运行的基石千万别图省事只刷不等。顺带说一个经验显式等待的timeout别设太死。一批挂掉的用例里很大比例都是因为等待时间刚好卡在临界点。给足余量比如10秒起步配合轮询机制多数情况下是能救回来的。5.4 强制刷新与缓存绕过的场景有些场景需要拿到页面最新数据但页面资源被浏览器缓存了刷新后还是旧样子。driver.refresh()不会强制清缓存这时候可以先清理缓存再刷新driver.execute_cdp_cmd(Network.clearBrowserCache, {}) driver.refresh()Network.clearBrowserCache是CDP里清空浏览器缓存的命令配合刷新使用能拿到的就是基本不带缓存的完整页面。对数据看板、实时行情这类数据必须最新的页面这个组合很实用。还有一种刷新不是刷整个页面而是只刷新局部数据比如定时轮询接口数据并局部更新DOM。这种场景下直接用JavaScript去触发页面的数据刷新函数就行不需要整页重载。前提是你对页面的内部结构足够了解能定位到那个刷新函数灵活性高但也脆页面改版就失效属于能用但不推荐的方案。6. 综合实战把三个功能串成一个巡检Demo6.1 需求与脚本设计纸上谈兵这么多来一个完整的实战。假设场景某数据展示页面每天凌晨数据更新需要自动巡检确认页面能正常打开、数据渲染完整、关键区域没有异常空白并且把巡检结果截图留档。功能拆解下来就是五步设置标准视口尺寸1920×1080打开目标页面等待关键数据区域渲染完成截图留档刷新页面等待二次渲染完成再截图对比两次数据是否一致这个Demo综合运用了本文的三项核心技能同时加入了等待和异常处理是一个可以直接抄作业的模板。6.2 完整代码实现import time from pathlib import Path from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class PageInspector: def __init__(self, url): self.url url self.driver webdriver.Chrome() self.driver.set_window_size(1920, 1080) def open_page(self): self.driver.get(self.url) WebDriverWait(self.driver, 15).until( EC.presence_of_element_located((By.ID, data-table)) ) def refresh_page(self): self.driver.refresh() WebDriverWait(self.driver, 15).until( EC.presence_of_element_located((By.ID, data-table)) ) def save_shot(self, name): ts time.strftime(%Y%m%d_%H%M%S) Path(./inspect_shots).mkdir(exist_okTrue) path f./inspect_shots/{name}_{ts}.png self.driver.save_screenshot(path) print(f截图已保存: {path}) def inspect(self): try: self.open_page() self.save_shot(before_refresh) # 读取刷新前的关键数据 before_text self.driver.find_element(By.ID, data-table).text self.refresh_page() after_text self.driver.find_element(By.ID, data-table).text self.save_shot(after_refresh) if before_text after_text: print(两次数据一致页面状态正常) else: print(两次数据不一致注意检查数据更新逻辑) finally: self.driver.quit() if __name__ __main__: inspector PageInspector(https://example.com/data) inspector.inspect()6.3 运行过程与结果解读代码跑起来后控制台先输出截图已保存: before_refresh_xxx.png说明页面首次打开、数据区域渲染完成、截图成功。随后执行刷新等到>driver.implicitly_wait(5)隐式等待会在每次查找元素时自动等待指定时间作为显式等待之外的第二道保险但别设太长5秒以内比较合理太长了会让失败用例的排查节奏变得迟钝。二是尽量固定浏览器配置关掉无用的动画和提示。自动化环境下禁用GPU加速、禁用弹窗能减少很多不可控因素options webdriver.ChromeOptions() options.add_argument(--disable-gpu) options.add_argument(--disable-notifications) options.add_argument(--disable-popup-blocking)三是截图不同于普通操作它依赖渲染结果所以截图前务必保证页面是稳定状态。所谓稳定不仅仅是元素存在还包括网络请求基本完成、动画基本结束。一种实用做法是轮询页面某个标志位比如等待某个异步加载的文案出现并且连续两次取值相同再执行截图。这三条经验看着琐碎但在真实项目里它们往往是脚本从能跑到稳定跑的分水岭。我接手过不少自动化项目最后稳定性的差距绝大多数都不在框架有多高级而在这些基础细节有没有做到位。说实话尺寸、截图、刷新这三个操作每个单拎出来都够不上高级技术但它们拼在一起就是自动化巡检、页面回归、视觉验证这些真实需求的底座。我早期写自动化脚本时也不太在意这些细节后来被生产环境一连串偶发失败教育过几次才回头把每个基础API的行为抠了一遍。现在带新人我也都是先让他们把这几个操作吃透把先设尺寸再开页面刷新必等待旧元素重新找这些习惯刻进肌肉记忆再去接触更复杂的框架。你的脚本稳不稳定往往不是输在复杂逻辑上而是输在这些最基础的操作细节里。
阅读完成 · 觉得有帮助?
咨询建站