ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Selenium自动化测试实战:从WebDriver原理到pytest框架整合

2026/10/6 19:56:46 拓冰建站 浏览量
Selenium自动化测试实战:从WebDriver原理到pytest框架整合 1. 从“点点点”到“自动化”Selenium到底能帮你解决什么做了几年自动化测试被问得最多的一个问题就是“我用Selenium到底能干啥”简单说Selenium是目前生态最成熟、社区最活跃的浏览器自动化测试工具。它帮你把手工操作浏览器的动作——打开页面、输入账号密码、点击按钮、翻页、截图——全部用代码接管。日常回归测试、多浏览器兼容性验证、UI层面的冒烟测试Selenium都是绕不开的主力工具。它的核心价值在于一套脚本多浏览器跑。同一段Python/Selenium脚本配好对应的WebDriver就能在Chrome、Firefox、Edge上执行。对于需要验证“不同浏览器下核心流程是否正常”的团队这个能力非常关键。从环境配置到框架整合H5端的兼容性验证也能用Selenium的移动模拟参数搞定。配合pytest做测试管理再接入数据驱动整套UI自动化工程就能落地。这篇文章我会把Selenium从原理到实战再到和pytest框架的整合完整拆解一遍偏向可直接照抄的经验。这篇文章适合谁刚上手UI自动化的测试工程师被领导要求“三天搭一套自动化框架”的背锅侠以及准备自动化测试岗位面试、想系统梳理知识点的同学。如果你只想找一段立刻能跑的脚本那下面的内容也能直接满足你——代码和步骤都会给全。2. Selenium的工作原理为什么必须有WebDriver2.1 WebDriver的“翻译官”角色很多初学者会觉得“Selenium就是模拟鼠标操作”。这个理解不准确也不利于后续排查问题。Selenium本身不是机器人它是“翻译官”。Python脚本写的driver.get(https://example.com)通过WebDriver协议转换成浏览器能够理解的原生指令再由浏览器执行。反向同理浏览器页面上的DOM元素信息也必须通过WebDriver一层层传回给Selenium代码。这个机制决定了几个关键点WebDriver和浏览器版本必须严格匹配否则翻译出错Selenium执行速度的快慢很大程度上取决于浏览器和页面本身而不是脚本逻辑代码跑不起来时先看Driver有没有崩再看元素有没有变。2.2 Selenium 4带来了什么变化2021年Selenium 4正式发布和3.x最大的区别是全面支持W3C WebDriver标准协议。这意味着脚本和WebDriver之间的通信不再走原有JSON Wire Protocol这种“外包翻译”而是走统一的标准协议——更稳、更快、兼容性问题更少。Selenium 4还内置了相对定位器Relative Locator可以用“某个按钮上方”“某个输入框左边”这类位置关系来定位元素对复杂页面的处理帮助很大。DevTools协议也开放了可以捕获网络请求、模拟网络慢速环境。如果你还在用Selenium 3的老项目我的建议是别一下全量升级先在一条核心业务脚本上验证WebDriver和浏览器的兼容情况稳定后再推广。盲目升级导致大量硬编码的xpath失效是团队从3.x切到4.x时最容易踩的坑。2.3 Selenium不是万能的认清边界热词里有appium自动化测试也有接口自动化测试很多同学会混淆这几个体系。简单给个区分Selenium浏览器自动化模拟用户在PC端浏览器上的行为。Appium移动端自动化模拟用户在手机App上的操作底层思路和Selenium很像。接口自动化直接验证服务端API返回的数据和UI无关最适合做大量快速回归。UI自动化适合核心流程的冒烟验证、多浏览器兼容性检查、回归成本高的重复场景。但不适合大规模数据计算类校验应该交给接口层去做也不适合频繁变化的页面每周改版一次的页面你的脚本维护成本会非常痛苦。我的实践经验是UI自动化永远只覆盖核心价值链路。登录、搜索、下单、支付这类主路径做UI自动化边角料功能交给接口自动化或手工回归这套分工是所有成熟团队的通用做法。3. 从零搭建Selenium环境工具选型和Driver管理3.1 Python生态下的核心选型当前最适合入门和落地的是Python配合Selenium库、pytest测试框架、WebDriver-Manager驱动管理工具这套组合足够应对绝大多数项目。安装代码pip install selenium pytest pytest-html webdriver-managerSelenium是核心库pytest负责用例管理和断言pytest-html用来生成漂亮的测试报告webdriver-manager帮我们自动下载和管理浏览器驱动。如果你用Java也可以选Java Selenium TestNG但脚本的简洁性和上手成本确实不如Python这也是目前UI自动化测试的主流趋势更偏Python的原因。3.2 为什么强烈推荐webdriver-manager早期版本没有webdriver-manager时团队里每个人都要手动去下载chromedriver放到指定目录然后在代码里写死路径。这种做法有致命问题Chrome一自动更新Driver版本对不上所有用例白色大屏报错。每台机器的Driver路径不一样脚本换机器就跑不了。越多人参与环境问题越多最终淹没真正的测试用例。webdriver-manager这个库就是干这个的首次运行自动检测本机Chrome版本下载对应的chromedriverDriver崩了会自动找新版它把环境问题直接从日常执行中拿掉了。配合Selenium 4的Service对象代码可以写成这样import pytest from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager pytest.fixture() def driver(): service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice) driver.implicitly_wait(10) yield driver driver.quit()这个driver方法是一个pytest fixture。yield之前的代码是前置准备用例执行结束后执行driver.quit()关闭浏览器每个用例都会自动获得一个全新的浏览器实例互不干扰。3.3 实用的浏览器配置选项Chrome支持的启动参数非常多日常实战最常用的几个from selenium.webdriver.chrome.options import Options def create_driver(): service Service(ChromeDriverManager().install()) options Options() options.add_argument(--start-maximized) # 窗口最大化 options.add_argument(--ignore-certificate-errors) # 忽略证书错误 options.add_argument(--disable-notifications) # 禁用通知弹窗 # headless模式适合CI环境无界面执行 # options.add_argument(--headlessnew) driver webdriver.Chrome(serviceservice, optionsoptions) driver.implicitly_wait(10) return driver--headlessnew是Chrome新版的无头模式影子DOM和页面渲染更接近真实浏览器。本地调试用有头模式能直观看到每一步操作CI环境、Jenkins跑回归时用无头模式。但要注意无头模式可能暴露一些有头模式看不到的时序问题比如页面异步加载资源的异常时序所以在无头模式下如果用例偶发失败先加合适的等待再排查代码逻辑。3.4 浏览器Driver版本异常排查既然搞自动化测试难免遇到Driver版本问题。最常见的是日志报错This version of ChromeDriver only supports Chrome version 114翻译成人话驱动版本和浏览器版本不匹配。之前手动管理时团队里天天有人问“我的Google Chrome怎么又更新了脚本怎么跑不了”。装了webdriver-manager基本能自动解决但如果网络下载过程中断导致Driver不完整或者公司内部浏览器做了定制版本webdriver-manager识别失败就需要手动到官方路径下载对应版本的Driver放到项目根目录下的drivers/文件夹再手动指定路径service Service(executable_path./drivers/chromedriver)排查异常问题时万能的第一招是看日志里有没有WebDriver的字样第二招是把本地Driver的路径配合--verbose参数再跑一遍第三招是直接检查浏览器版本输出文件的版本号。三年排障下来的经验80%的Selenium运行异常根因是环境不是代码。4. 元素定位与页面操作半天讲清楚核心细节4.1 八种定位方式怎么选元素定位是UI自动化最核心、最影响稳定性的环节。Selenium提供的定位方式有8种id、name首选页面中通常唯一编写速度最快。class name、tag name适合批量定位同类型元素。link text、partial link text仅适用于a标签常用于精确或模糊匹配超链接文本。xpath万金油功能最强但写法和维护成本高。CSS selector性能和健壮性在xpath之上语法简洁。实际经验来看优先顺序是id name CSS selector xpath。现在还流行配合相对定位器比如定位某个按钮“上方”的输入框这在表单类页面很实用from selenium.webdriver.support.relative_locator import locate_with input_ele driver.find_element( locate_with(By.TAG_NAME, input).above(driver.find_element(By.ID, submit)) )少用tag_name定位页面中同类标签太多返回的往往是第一个或随机一个脚本很难有稳定性。能用id解决绝不上xpath是自动化测试老司机一致认同的铁律。4.2 XPath高级用法与追根溯源接手老项目时经常看到满屏的//*[idapp]/div[2]/div[3]/div[1]这种绝对路径xpath页面结构稍微调一下脚本就全崩了。正确做法是用文本定位按钮//button[contains(text(), 登录)]结合属性定位和层级缩小范围//div[contains(class, modal)]//input[typepassword]用位置辅助(//input[typecheckbox])[2]尽量写“相对xpath”核心思路是从稳定父节点往下找。例如submit_button driver.find_element(By.XPATH, //div[classlogin-box]//button[contains(text(),立即登录)])这段代码先锁定classlogin-box的登录容器再找容器里面的“立即登录”按钮页面顶部改动再多也不会影响它。注意xpath索引是从1开始的不是从0开始初学面试经常有人在这里翻车。还有一个隐藏细节浏览器开发者工具里右键复制的xpath都是绝对路径可以直接用但只适合调试不适合写进测试用例长期维护。每次写元素定位时多花30秒想清楚“这个元素最稳定的属性是什么”未来能省下几个小时的排障时间。4.3 表单操作与下拉框、单选框输入框用send_keys()填入内容点击用click()这两个是最高频的操作。真实项目踩坑最多的是下面这些细节输入框要清空再填入避免脏数据残留element.clear() element.send_keys(测试用户)单选框、复选框存在已经选中的情况点击前要判断一下当前状态否则会把“已勾选”变成“未勾选”checkbox driver.find_element(By.XPATH, //input[nameagree]) if not checkbox.is_selected(): checkbox.click()处理下拉框Selenium提供了专用的Select类from selenium.webdriver.support.ui import Select city Select(driver.find_element(By.NAME, city)) city.select_by_value(beijing) # 按value属性选择 city.select_by_visible_text(北京) # 按下拉显示的文本选择隐藏元素不能直接点击或填入数据先调整元素属性或用JavaScript执行点击。用JavaScript点击是很多老手处理顽固遮挡层的技巧但不要依赖核心操作还是要符合真实用户行为。4.4 鼠标和键盘操作上传也能模拟自动化的本质是模拟用户鼠标、键盘、文件上传这三类操作必须掌握from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys actions ActionChains(driver) # 悬停到导航菜单上触发悬浮子菜单 menu driver.find_element(By.ID, nav) actions.move_to_element(menu).perform() # 双击某个单元格 cell driver.find_element(By.CSS_SELECTOR, .editable-cell) actions.double_click(cell).perform() # 右键打开上下文菜单 actions.context_click(cell).perform() # 键盘组合键Ctrl A 全选Ctrl C 复制 actions.key_down(Keys.CONTROL).send_keys(a).key_up(Keys.CONTROL).perform()文件上传其实是UI自动化里最另类的操作——大多数上传控件本质是input标签直接用send_keys()传入本地文件路径就能完成根本不需要弹出系统文件选择框。driver.find_element(By.CSS_SELECTOR, input[typefile]).send_keys(/Users/test/upload_report.xlsx)如果是高版本的Chrome且input为隐藏状态可以用execute_script移除隐藏属性再走send_keys()。真正需要模拟系统弹窗上传的罕见场景再用AutoIT或pywinauto库但优先级非常靠后。5. 等得到页面真加载完等待策略是稳定性的分水岭5.1 三种等待机制对比很多脚本写的挺好跑起来却老是“找不到元素”大概率是等待策略没用好。Selenium有三大类等待等待类型写法原理推荐指数强制等待time.sleep(3)无论页面状态死等3秒不推荐隐式等待driver.implicitly_wait(10)每次查找元素时最多等待10秒基础配置显式等待WebDriverWaitexpected_conditions等待某个条件成立最多等N秒强烈推荐强制等待的问题在于“一刀切”快的时候浪费几秒钟慢的时候又等不够跑完整个套件要多花几倍时间。隐式等待的问题是只等元素出现不能判断元素是否可点击也容易出现“元素存在了但还不能交互”的尴尬情况。显式等待精准、灵活是提升用例稳定性的最优解。5.2 显式等待的正确打开方式显式等待的完整写法from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待登录按钮可见且可点击 login_btn WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, login-button)) ) login_btn.click() # 等待新页面里某个标题出现常用于切换页面的场景 WebDriverWait(driver, 10).until( EC.visibility_of_element_located((By.XPATH, //h1[contains(text(), 欢迎)])) )结合expected_conditions还能判断元素消失、元素存在、URL变化、弹窗出现、iframe可切换等。实战中我见过很多新手只会在until()里写EC.presence_of_element_located结果元素存在但被遮挡时依然点击报错。正确做法是区分场景只需要元素在DOM里就够的用presence_of_element_located。元素必须可见才能操作用visibility_of_element_located。元素必须可见且可点击用element_to_be_clickable。等待页面加载完成简单粗暴的办法是等一个稳定的页面特征元素出现。5.3 页面加载慢的终极解法显式等待能解决绝大多数问题但偶尔会遇到“页面一直在加载找元素找不到不找元素又超时”的玄学问题。这不仅和网络有关也和页面临时加载了外部资源有关。再看一遍WebDriver的执行机制——find_element执行时浏览器要做的是先在当前DOM里找元素如果没找到就等一段时间看隐式等待时间。也就是说隐式等待和显式等待混用可能产生叠加等待效果尤其是有隐性加载的页面最长等待时间会达到两者相加的时长。一个成熟的框架会在Global设置里直接禁用隐式等待所有等待全部由显式等待完成保持流程可控。也可设置页面加载策略# 等页面complete事件最严格 options.page_load_strategy normal # 等DOMContentLoaded事件稍快 options.page_load_strategy eager # 不等加载完成适合图片过多的页面 options.page_load_strategy none driver.get(url)碰到图片一大堆还带广告的页面把策略调成eager或none配合显式等待核心元素出现比干等十几秒强得多。6. 破解疑难场景iframe弹窗、多窗口、断言与JavaScript6.1 iframe切换元素明明在页面上却报找不到这是新手翻车率最高的场景之一。页面上有内嵌框架iframe时Selenium默认只能在顶层文档里找元素不切换进去永远找不到。切换有三种情况# 通过id/name切换 driver.switch_to.frame(mainFrame) # 通过索引切换0表示第一个iframe driver.switch_to.frame(0) # 定位iframe元素后传入 iframe_ele driver.find_element(By.CSS_SELECTOR, iframe#oauth) driver.switch_to.frame(iframe_ele) # 操作完切回主文档 driver.switch_to.default_content()做第三方登录授权或登录框嵌套在iframe里的项目时这块一定要在代码里加健壮判断。先确认当前页面是否有iframe再决定是否切换。简单判断iframe有没有存在的方法就是先在顶层文档查找元素找不到再用WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.ID, frame)))一条语句完成等待和切换。6.2 alert弹窗和浏览器多窗口模拟用户也是要处理弹窗的常见的alert弹窗有三种# 接收alert点击确认 driver.switch_to.alert.accept() # 拒绝alert点击取消 driver.switch_to.alert.dismiss() # 获取alert上的文本 text driver.switch_to.alert.text # 输入内容后确认prompt弹窗 driver.switch_to.alert.send_keys(输入内容) driver.switch_to.alert.accept()多窗口处理则是另一个高频需求。点击一个链接打开新标签页后续的操作全在新窗口需要做窗口切换# 获取当前窗口句柄 main_window driver.current_window_handle # 点击后等待新窗口出现 WebDriverWait(driver, 10).until(EC.number_of_windows_to_be(2)) # 切换到新窗口 for handle in driver.window_handles: if handle ! main_window: driver.switch_to.window(handle) break切换窗口的关键是先记录下来原来的窗口句柄最后要回到原窗口时再用switch_to.window(main_window)。不少自动化测试面试题会专门问这里其实原理不复杂浏览器是多窗口的Selenium一次只控制一个窗口所有操作都基于当前window handle。6.3 断言测试脚本怎么才算“通过”断言是判断用例成败的关键pytest断言语法简单def test_login(driver): # 执行登录操作... assert driver.title 用户中心 # 或断言URL assert user-center in driver.current_url # 或断言页面某个元素出现 success_text driver.find_element(By.XPATH, //div[contains(class,success)]).text assert 登录成功 in success_text这里的核心是断言要落在结果校验点上而不是只断言操作没报错。很多新手的用例“跑完没报错就绿了”实际上页面是否真的进入了目标状态完全没校验这种用例价值很低。断言文本时注意element.text属性只读取可见文本包括子元素的文本如果页面里用了隐藏元素包裹可能读到的内容并不是你想要的需要用get_attribute(textContent)来替代。6.4 JavaScript执行数组操作与滚动执行为什么要执行JavaScript最典型的场景是滚动页面让元素进入可视区域。某些定位比较靠下如果不滚动直接点击会报“element is not clickable at point”。老手的解法是driver.execute_script(arguments[0].scrollIntoView(true);, target_element)或者做全页面滚动到底部加载更多内容的操作driver.execute_script(window.scrollTo(0, document.body.scrollHeight);)获取input的value值也需要JavaScriptvalue driver.execute_script(return arguments[0].value, input_ele)6.5 模拟移动端补齐H5测试项目需要兼容H5页面时直接用Chrome的移动模拟功能mobile_emulation { deviceMetrics: {width: 375, height: 812, pixelRatio: 3.0}, userAgent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2 like Mac OS X) } options Options() options.add_experimental_option(mobileEmulation, mobile_emulation) driver webdriver.Chrome(serviceservice, optionsoptions)这样可以模拟手机浏览器的宽度和UA用来测H5核心流程问题不大。但注意它不如Appium真实不支持完整的触控手势和原生App能力。H5项目用Selenium模拟原生App走Appium——这个是自动化测试领域非常常见的分工。7. 搭一套能落地的自动化测试框架pytest整合实战7.1 为什么是pytest而不是unittestPython自带的unittest可以用但在项目组织、断言灵活性、失败重跑、测试报告这些方面pytest优势明显断言直接用Python内置assert不用记一大堆self.assertEqual方法。fixture体系强大支持作用域、自动执行、并发。插件生态好pytest-html生成报告pytest-rerunfailures处理重试pytest-xdist支持并行执行。自动发现测试用例命名规则规范。UI自动化的运行时长普遍偏长用例量大以后并行执行能力很关键。pytest配合xdist多进程跑不同浏览器整个回归时长能压缩到原来的四分之一。7.2 fixture设计数据清理与复用结合前面写的fixture完整的示例import pytest import time from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options pytest.fixture(scopefunction) def driver(): service Service(ChromeDriverManager().install()) options Options() options.add_argument(--start-maximized) options.add_argument(--ignore-certificate-errors) driver webdriver.Chrome(serviceservice, optionsoptions) driver.implicitly_wait(10) yield driver driver.quit()scopefunction表示每个测试函数都启动一个全新的浏览器用例间互不影响。如果某个场景需要共用浏览器就用scopemodule或scopesession在大规模执行时能显著提升速度但要注意用例之间的状态隔离问题。fixture还能做数据准备比如先在接口层创建一个测试账号再登录UIpytest.fixture() def logged_in_driver(driver): driver.get(https://example.com/login) driver.find_element(By.ID, username).send_keys(test_user) driver.find_element(By.ID, password).send_keys(123456) driver.find_element(By.ID, login-btn).click() WebDriverWait(driver, 10).until( EC.visibility_of_element_located((By.CSS_SELECTOR, .user-info)) ) return driver7.3 数据驱动一个脚本跑多组数据自动化测试最忌讳把参数全部写死在用例里。用pytest的params做数据驱动一组数据一条用例import pytest pytest.mark.parametrize(username,password,expect, [(user1, pass1, 登录成功), (user2, wrong, 密码错误), (, pass3, 用户名不能为空)]) def test_login(driver, username, password, expect): driver.get(https://example.com/login) driver.find_element(By.ID, username).send_keys(username) driver.find_element(By.ID, password).send_keys(password) driver.find_element(By.ID, login-btn).click() result driver.find_element(By.CSS_SELECTOR, .toast-message).text assert expect result这比写3个几乎相同的用例高效得多。要加新数据组合只改参数列表就行。如果测试数据量庞大建议数据放到excel、CSV或yaml文件里用pytest的钩子函数动态加载避免改动用例代码。7.4 测试报告与失败截图UI自动化用例失败后如果没有现场截图排查问题全靠猜。在用例失败时自动截图是框架必备能力。pytest的钩子函数实现import os import pytest from datetime import datetime pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: driver item.funcargs.get(driver) if driver: timestamp datetime.now().strftime(%Y%m%d_%H%M%S) screenshot_dir screenshots os.makedirs(screenshot_dir, exist_okTrue) driver.save_screenshot(f{screenshot_dir}/{item.name}_{timestamp}.png)生成Html报告的执行命令pytest test_cases/ -v --htmlreport.html --self-contained-html --reruns1 --reruns-delay 2--htmlreport.html生成报告--self-contained-html把样式内嵌入文件方便邮件和CI传递。--reruns1表示失败重跑1次--reruns-delay 2重跑前等2秒。这个对网络波动引起的偶发问题很有效但不能滥用——如果一个用例反复重跑说明问题不是偶发而是定位或等待策略有缺陷。7.5 Page Object模式测试代码不腐化让框架长期可维护的套路就是Page Object页面对象模型。每个页面封装成一个类把元素定位和操作逻辑收进类里测试只负责调用页面方法。拿登录页举例class LoginPage: def __init__(self, driver): self.driver driver def open(self): self.driver.get(https://example.com/login) return self def input_username(self, username): self.driver.find_element(By.ID, username).send_keys(username) def input_password(self, password): self.driver.find_element(By.ID, password).send_keys(password) def click_login(self): self.driver.find_element(By.ID, login-btn).click() def login(self, username, password): self.open() self.input_username(username) self.input_password(password) self.click_login()测试用例就变得非常干净def test_user_login(driver): login_page LoginPage(driver) login_page.login(test_user, 123456) assert driver.current_url https://example.com/user-center这个模式带来的好处页面改动时只改一个类所有用例自动适配元素定位集中管理不像以前那样散布在几十个用例里新同事接手时看类结构就能知道页面有哪些操作。这是从“能跑就行”的脚本到“能长期维护的框架”之间最关键的架构转换。8. 资深从业者的避坑心得自动化测试的执行与维护在真实项目中把Selenium自动化跑得又稳又省心光会写代码是不够的下面这些“从坑里爬出来”的经验纯属实战沉淀普通文档里不会写。第一脚本要跑得快就要去掉不必要的等待。默认的显式等待时间设短一点比如5秒而不是动不动20秒。能并发跑就并发跑pytest-xdist并行执行能节省大量时间。如果用例多到要排队执行瓶颈往往不是selenium本身而是测试数据的冲突——多进程操作同一批账号互相挤下线结果到处失败。第二测试数据独立是UI自动化稳定性的基石。每个用例跑前都创建专属数据跑完清理保证用例可重复执行。数据从UI上创建成本高最快的办法是调用接口准备数据。工程化的UI测试框架往往大量使用API预置数据UI只做核心操作和结果验证。这也是很多高级自动化工程师的标配思路。第三偶尔失败的用例要区分对待。如果一次失败重跑就过了这属于不稳定用例可以加等待或加重跑。如果连续多次都失败不要通过重跑掩盖那说明页面结构变了赶快修定位器。团队里长期无人维护的用例会腐烂最后变成摆设。定期的用例巡检和修复是确保自动化和业务同步的日常功课。第四Selenium脚本跑起来的实际价值体现在持续回归上。接入Jenkins定时任务每天凌晨跑一次早上来公司就能看到测试报告。核心业务改动跑一遍自动化冒烟比人工点半小时靠谱得多。测试报告里的失败率趋势还能反向帮助开发团队发现代码质量变化——这是一份Selenium脚本能带来的超预期回报。最后再说说热词里那个“uds自动化测试输出测试报告”。很多UI和嵌入式相关的测试输出报告的形式差异巨大。Selenium结合pytest-html能生成漂亮的Web报告再配合邮件推送团队里每个人都能及时看到结果。无论什么领域自动化的价值都不在于“变成无人值守”而在于把人的时间和注意力从重复点击中解放出来集中在真正的分析上。这也是自动化测试始终被重视的核心原因。