免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Python+Selenium+pytest:从零搭建Web自动化测试框架实战

Python+Selenium+pytest:从零搭建Web自动化测试框架实战 1. 为什么是Python Selenium先想明白你要解决什么问题先说一个真实的场景。某公司有个Web后台管理系统每次发版前测试组的同学要手动点一遍二十多个核心流程点错一步就要从头来一轮下来四十分钟起步。后来版本节奏加快从一个月一版变成一周一版手工回归实在顶不住了才决定做自动化测试。选型的时候团队里有人提议用Selenium理由是团队里会Python的人多、文档多、出了坑容易搜到答案。当时也有人质疑Selenium是不是太老了现在不是有更新一代的工具吗我得说这个质疑有一定道理但不能一概而论。Selenium走的是WebDriver协议这个协议已经是W3C的行业标准几乎所有主流浏览器都原生支持。意味着你用Selenium写的脚本在Chrome上能跑在Edge上能跑Firefox上也能跑换浏览器不需要重写脚本。这一点对于需要兼容多浏览器的项目来说是实打实的优势。像Playwright、Cypress这些新工具确实在很多方面更顺手但它们对浏览器生态的绑定、对测试场景的假设有时候并不适合你们现有的手工测试流程和团队技术栈。Python的优势则在于语法足够简洁写业务逻辑的时间远小于写语言本身的时间。对于测试团队来说成员不一定是专职开发可能是手工测试转过来的Python几乎是上手成本最低的选择之一。再加上pytest这个测试框架的加持用例编写、断言、夹具、报告生成、重试机制全都有现成的方案。所以这篇文章不是要告诉你Selenium天下第一而是基于我实际把Python Selenium pytest这套组合做成一个可落地、可维护、可扩展的自动化测试框架的经验把搭建过程中最关键的设计决策、代码实现和坑点一次性讲清楚。适合刚接触自动化测试不久、想搭一套正经框架而不是写一堆一次性脚本的读者也适合已经写了不少Selenium脚本、但感觉维护成本越来越高的同学。2. 环境准备版本匹配才是第一个真正的坑很多人栽在环境上不是不会装Python而是被浏览器驱动和Selenium版本之间的兼容关系搞崩溃。这部分的坑如果不在最开始排掉后面每一步都会串味。2.1 Python环境与依赖安装我建议直接用Python 3.9以上的版本别再用2.7了Selenium 4已经完全放弃Python 2支持。安装这一步没什么好说的去官网下载对应操作系统的安装包安装时记得勾选Add Python to PATH。装完在命令行敲一下python --version能输出版本号就说明成了。接着是虚拟环境。这一步很多人偷懒跳过直接把包装到全局环境里时间一长就会遇到依赖冲突。比如某项目需要selenium 3.x另一个需要selenium 4.6以上两个项目互相踩。用内置的venv就能解决python -m venv venv激活虚拟环境在Windows上是venv\Scripts\activateLinux或macOS是source venv/bin/activate。然后安装依赖pip install selenium pytest pytest-html pytest-rerunfailures pyyaml我多说一句pytest-rerunfailures这个包听着不起眼在实际跑自动化的时候几乎救场神器。后面讲用例稳定性会细说。2.2 浏览器驱动WebDriver版本与浏览器版本必须严格对应Selenium本身不操作浏览器它是通过WebDriver这个桥接程序把指令翻译给浏览器执行的。Chrome的驱动叫ChromeDriverFirefox的驱动叫GeckoDriver。最大的坑在于Chrome浏览器在频繁更新ChromeDriver如果和浏览器版本对不上程序启动时就会报SessionNotCreatedException这种错误信息还说得云里雾里。我举个例子。某次我这边Chrome升级到某个版本但ChromeDriver还停留在上一个版本跑脚本时就报错。那时候Selenium 4.6还没出来我只好手动去下载匹配的新版驱动。这种事发生过不止一次每次升级浏览器都要留个心眼。手动管理驱动的步骤是先在浏览器地址栏输入chrome://version查看版本号然后去ChromeDriver的下载页面选择同主版本的驱动文件下载后放到系统PATH目录下或者直接在代码里用Service对象指定驱动路径。Selenium 4.6之后情况好了很多它内置了Selenium Manager可以在首次运行时自动匹配并下载对应的浏览器驱动。对新手来说这真的是省了一大半心。但自动匹配也不是万能的在某些内网隔离环境里下载不了驱动或者公司统一管控浏览器的版本你就得回到手动管理模式。所以我建议知道自动管理的存在但也必须掌握手动指定驱动的能力实际项目中两种方案都会用到。from selenium import webdriver from selenium.webdriver.chrome.service import Service # 手动指定驱动路径 service Service(/path/to/chromedriver) driver webdriver.Chrome(serviceservice) # Selenium 4.6 默认方式无需手动指定 driver webdriver.Chrome()2.3 验证环境的小冒烟脚本装完之后不要急着写框架先跑一个最小脚本验证整条链路是通的from selenium import webdriver driver webdriver.Chrome() driver.get(https://www.baidu.com) print(driver.title) driver.quit()如果这一步能正常打印出页面标题说明Python、Selenium、驱动和浏览器之间的通路已经打通。后续所有复杂的框架设计都是建立在这个小小通路上面的。这一步跑不通别急着往下走先把环境问题解决干净。3. 先搭好框架的地基目录结构、配置管理与日志模块很多测试脚本写到后面改不动不是因为代码逻辑有多复杂而是从一开始就没有规划好结构。所有代码糊在两个文件里定位器散落在各处URL写死在脚本里换环境要改十几个地方。这种脚本维护起来比重新写一遍还痛苦。3.1 目录结构设计我常用的一个目录结构是下面这样的可以根据实际项目调整project_root/ ├── config/ │ ├── config.yaml │ └── __init__.py ├── data/ │ ├── test_data.yaml │ └── __init__.py ├── drivers/ │ └── chromedriver ├── logs/ ├── pages/ │ ├── base_page.py │ ├── login_page.py │ └── __init__.py ├── testcases/ │ ├── conftest.py │ ├── test_login.py │ ├── test_workflow.py │ └── __init__.py ├── utils/ │ ├── logger.py │ ├── browser_engine.py │ └── __init__.py ├── reports/ ├── requirements.txt └── run.py简单解释一下config放配置data放测试数据pages是页面对象层testcases放用例utils放工具模块drivers放驱动文件logs和reports放运行产物。这个结构遵循的核心理念是分层测试用例不直接操作浏览器细节而是调用页面对象页面对象不直接读取数据而是从配置和数据文件取。每一层都知道自己该干什么不该干什么改动的影响范围就能控制住。3.2 配置外置环境切换不应该改代码配置管理的核心诉求是测试环境、预发环境、生产环境切换时不应该动任何代码。推荐用YAML格式的配置文件可读性好还支持嵌套结构。# config/config.yaml browser: name: chrome # chrome / firefox / edge headless: false # 无头模式CI上跑建议true implicit_wait: 5 # 隐式等待秒数 page_load_timeout: 30 options: - --disable-gpu - --no-sandbox base_url: https://test.example.com login: username: test_user password: test_password screenshots: enabled: true directory: screenshots读取配置用PyYAML就行几行代码搞定import yaml import os with open(os.path.join(os.path.dirname(__file__), config.yaml), r, encodingutf-8) as f: config yaml.safe_load(f)不同环境怎么切换最粗暴但实用的做法是准备多个配置文件config.yaml、config_staging.yaml、config_prod.yaml通过环境变量指定import os import yaml env os.getenv(TEST_ENV, dev) config_file fconfig_{env}.yaml if env ! dev else config.yaml如果你需要传敏感信息比如密码建议不要硬编码在YAML里而是通过环境变量注入。config.yaml里写login: ${LOGIN_PASSWORD}读取时检测到${...}就自动从环境变量取值。这个小细节能避免很多账号密码泄露在代码仓库的尴尬场景。3.3 日志模块排查问题时的第一根救命稻草没有日志的自动化测试框架跑挂了就只知道红了不知道是为什么红的。你只能本地跑一遍、打断点、猜原因。有了日志每次运行的全部操作都能留下痕迹。我一般是基于Python内置的logging模块做一层封装也用过loguru这个第三方库唯一的感觉是后者配置更省事格式化输出也更漂亮。但考虑到团队其他人已经熟悉logging直接用标准库也完全够用。# utils/logger.py import logging import os from datetime import datetime def setup_logger(): log_dir logs os.makedirs(log_dir, exist_okTrue) logger logging.getLogger(auto_test) logger.setLevel(logging.DEBUG) fmt logging.Formatter(%(asctime)s | %(levelname)s | %(message)s) # 文件日志 file_handler logging.FileHandler( os.path.join(log_dir, ftest_{datetime.now().strftime(%Y%m%d_%H%M%S)}.log), encodingutf-8 ) file_handler.setFormatter(fmt) file_handler.setLevel(logging.INFO) logger.addHandler(file_handler) # 控制台日志 console_handler logging.StreamHandler() console_handler.setFormatter(fmt) console_handler.setLevel(logging.DEBUG) logger.addHandler(console_handler) return logger logger setup_logger()注意一个细节日志中要包含足够多的上下文。比如点击某个按钮时把按钮的定位方式、所在页面、操作结果全部记下来。这样出了问题看一眼日志就能定位到是在哪一步、哪个元素上挂的不用在代码里翻来翻去。4. 页面对象模式让用例不再写成面条代码自动化测试脚本最常见的死法是用例代码里直接写driver.find_element(By.XPATH, ...)一个用例里三五十行这种代码定位器散落得到处都是。UI一改几十个测试方法跟着改改到怀疑人生。页面对象模式Page Object Model就是为了解决这个问题而产生的。核心思想很简单把每一个页面抽象成一个类类里面封装这个页面上的元素定位方式和操作方法。测试用例只跟页面对象打交道不直接操作WebDriver API。4.1 BasePage封装通用操作先实现一个BasePage把显式等待、元素查找、点击、输入、截图等通用操作都沉淀在这里。所有页面对象都继承它。# pages/base_page.py from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By class BasePage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) def find_element(self, locator): return self.wait.until(EC.presence_of_element_located(locator)) def find_clickable_element(self, locator): return self.wait.until(EC.element_to_be_clickable(locator)) def click(self, locator): elem self.find_clickable_element(locator) elem.click() logger.info(f点击元素: {locator}) def input_text(self, locator, text): elem self.find_element(locator) elem.clear() elem.send_keys(text) logger.info(f输入文本: {locator} {text}) def get_text(self, locator): elem self.find_element(locator) return elem.text def is_visible(self, locator): try: self.wait.until(EC.visibility_of_element_located(locator)) return True except Exception: return False这里有几个容易忽略的设计点。第一find_element用的是presence_of_element_located还是visibility_of_element_located语义不同前者只要求元素在DOM里后者要求元素可见。点击操作必须等元素可点击输入操作最好等元素可见后再操作。第二每次操作都打日志这在排查间歇性失败时几乎决定了效率。4.2 登录页封装一个可以对着抄的例子拿最常见的登录页面来演示。假设登录页有用户名输入框、密码输入框、登录按钮、错误提示区域对应封装如下# pages/login_page.py from selenium.webdriver.common.by import By from pages.base_page import BasePage class LoginPage(BasePage): USERNAME (By.NAME, username) PASSWORD (By.NAME, password) SUBMIT_BTN (By.XPATH, //button[typesubmit]) ERROR_TIP (By.XPATH, //div[contains(class, error)]) def login(self, username, password): self.input_text(self.USERNAME, username) self.input_text(self.PASSWORD, password) self.click(self.SUBMIT_BTN) def get_error_message(self): return self.get_text(self.ERROR_TIP)你会发现定位器全部是类级别的常量集中放在页面类的顶部。以后前端改了用户名输入框的name属性我只需要改这一处所有调用login()方法的用例都不受影响。这就是页面对象模式最大的价值将页面的变化隔离在页面对象内部不让它传导到测试用例层。4.3 业务动作封装粒度要刚刚好页面对象方法的设计粒度很有讲究。太粗了用例不好控制流程太细了用例里全是一行行的page.click()、page.input_text()调用又回到了当年那种面条代码的味道。我的经验是页面对象的方法应该描述用户在这个页面上能做什么事而不是这个页面上有哪些控件可以操作。比如登录页你封装一个login(username, password)就够了不要暴露input_username()和click_submit()给用例层。用例层关心的是我登录了而不是我输入了用户名、我输入了密码、我点了按钮。这个道理听上去简单但很多项目做着做着就变形。原因在于写代码的人图省事页面对象直接变成定位器容器。等用例超多之后才开始后悔。所以从一开始就要确立一个约束测试用例代码里不允许出现By.这类定位器相关的内容。做不到这一点的页面对象设计就不合格。5. 用例编排与执行引擎pytest是如何接管全局的页面对象层做好了剩下的就是把用例组织起来、让pytest去执行。这一部分我要重点讲两个东西fixture机制和测试数据驱动。5.1 conftest.py把driver的生命周期管理起来pytest的conftest.py是个很神奇的东西里面定义的fixture不需要import就能被同一目录及子目录下的所有测试用例使用。我在这里定义了driver的创建和销毁逻辑。# testcases/conftest.py import pytest from selenium import webdriver from selenium.webdriver.chrome.service import Service from utils.logger import logger pytest.fixture(scopeclass) def driver(): 每个测试类跑完自动关闭浏览器 options webdriver.ChromeOptions() # 读取配置决定是否无头 if config[browser][headless]: options.add_argument(--headlessnew) for opt in config[browser][options]: options.add_argument(opt) service Service(drivers/chromedriver) wd webdriver.Chrome(serviceservice, optionsoptions) wd.implicitly_wait(config[browser][implicit_wait]) wd.maximize_window() logger.info(浏览器启动完成) yield wd wd.quit() logger.info(浏览器已关闭)关键点在于yield之前的代码是前置动作之后的代码是清理动作。测试用例中途挂掉也不要紧清理动作照样执行不会留下僵尸进程。5.2 fixture传递driver给用例测试类直接用函数参数接收fixture即可# testcases/test_login.py import pytest from pages.login_page import LoginPage class TestLogin: def test_login_success(self, driver): driver.get(config[base_url]) login_page LoginPage(driver) login_page.login(admin, admin123) assert login_page.is_visible((By.XPATH, //span[text()首页])) def test_login_failed_with_wrong_password(self, driver): driver.get(config[base_url]) login_page LoginPage(driver) login_page.login(admin, wrong_password) error_msg login_page.get_error_message() assert 用户名或密码错误 in error_msg注意第二个用例这里验证的是业务逻辑。断言的是错误提示包含预期文案这是UI自动化最符合直觉的断言方式但也容易被文案变动影响。如果前端把提示文案改成账号或密码有误用例就会误报。所以这类断言建议优先找稳定的属性特征比如错误提示区域的id或class其次才是文案内容。5.3 测试数据分离同样的用例不同的数据登录用例不止要测一组数据可能几十组不同账号、不同密码、不同角色。如果每组数据写一条用例代码量成倍增加。用pytest的pytest.mark.parametrize可以优雅解决import pytest import yaml import os # 从data/test_data.yaml读取数据 with open(data/test_data.yaml, encodingutf-8) as f: login_data yaml.safe_load(f)[login_cases] pytest.mark.parametrize(username,password,expected, [ (case[username], case[password], case[expected]) for case in login_data ]) def test_login_with_params(driver, username, password, expected): driver.get(config[base_url]) page LoginPage(driver) page.login(username, password) if expected[type] success: assert page.is_visible((By.XPATH, expected[locator])) else: assert expected[message] in page.get_error_message()数据驱动的好处不只是减少重复代码更关键的是让非技术人员也能参与进来测试经理、手工测试同学只需要维护YAML或Excel里的测试数据不需要碰代码。5.4 用例分层与标记用例多了以后每次跑全部用例不现实。我一般会给用例打标记让pytest只跑指定层级的用例pytest.mark.smoke def test_login_success(self, driver): ... pytest.mark.regression def test_workflow_full_process(self, driver): ...跑冒烟用例pytest -m smoke。跑全量回归pytest -m smoke or regression。这个习惯能帮你们在快速验证核心功能和全量回归之间灵活切换效果立竿见影。5.5 断言失败自动截图关键的证据留存用例失败时只看到一条红色报错是不够的你根本不知道页面当时长什么样。解决方案是在失败时自动截图。写一个pytest hook# conftest.py 中补充 import os 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: screenshot_dir screenshots os.makedirs(screenshot_dir, exist_okTrue) file_name f{item.name}_{datetime.now().strftime(%Y%m%d_%H%M%S)}.png driver.save_screenshot(os.path.join(screenshot_dir, file_name))这样每次失败都有现场截图回头定位问题快得多。6. 报告、失败重试与CI集成让测试结果能被所有人看懂写到这里框架已经能跑起来了但离好用还有一步。一个自动化测试框架要真正融入团队流程报告和稳定性机制这两块必不可少。6.1 测试报告别让审核的人去翻控制台pytest本身只输出简单的控制台结果看不懂的人看了也白看。两个常用的方案是pytest-html和Allure。我建议有条件的项目直接用Allure报告美观、支持历史趋势、可以按功能模块分组筛选。先装Allure命令行工具这步依赖Java运行环境然后在pytest里生成Allure需要的数据文件pytest --alluredirreports/allure-results查看报告前启动本地服务allure serve reports/allure-results如果想追求轻量pytest-html就够了最多加一行代码就能配置# conftest.py pytest.hookimpl(wrapperTrue) def pytest_runtest_makereport(item, call): ...在pytest.ini里指定输出文件[pytest] addopts -v -s --htmlreports/report.html --self-contained-html我个人感受是HTML报告更符合大多数团队的查看习惯Allure胜在展示维度和历史记录。先不用纠结选一样装上实际跑几次就知道哪个适合你们了。6.2 失败重试机制治标但在UI自动化里是必要的UI自动化最大的痛点是环境因素导致的不稳定网络慢了一下、页面加载卡了一秒、前端渲染延迟用例就红了。如果你为此每条用例都加超长等待整体执行时间又受不了。pytest-rerunfailures的意义就在这里。配置如下[pytest] addopts -v -s --reruns 2 --reruns-delay 1 --htmlreports/report.html --self-contained-html意思是每条用例失败后额外重试2次每次间隔1秒。但必须明确重试机制是治标工具不是掩盖问题的遮羞布。如果一条用例频繁重跑也过不了说明它背后有真实的稳定性问题必须单独排查。我的习惯是重试后仍然失败的用例截图和日志都会自动保存方便事后分析。6.3 跑完怎么告知大家结果通知的实用做法自动化跑完结果要能触达到团队。最简单的方案是写一个执行入口脚本按返回值判断结果再通过企业微信或邮件等工具的Webhook推送。# run.py import subprocess import sys def main(): result subprocess.run( [pytest, -m, regression, --alluredirreports/allure-results], capture_outputTrue, textTrue ) if result.returncode 0: send_notification(回归通过) else: send_notification(f回归失败: {result.stdout[-500:]}) if __name__ __main__: main()这里的send_notification可以根据你们的沟通工具去适配核心逻辑都一样用subprocess跑pytest拿到退出码退出码是0代表全绿否则失败然后把关键信息推送出来。6.4 CI集成让自动化在每次发版前自动执行手动在本地跑自动化本质上和手工测试没有很大区别只是工具高级了一点。真正的提效是把框架接入CI流水线每次发版前自动触发跑一遍。我以常见的CI流程为例描述一下该配置哪些步骤拉取代码、安装依赖、启动被测系统的测试环境、执行pytest、收集Allure报告、根据退出码判断流程是否继续。这个流程不需要我写过深的CI配置但需要注意一个细节——CI环境里的浏览器必须是可用的。很多时候报错的根源是CI容器里没有装Chrome或者缺少GPU导致无法启动。对策是开启无头模式并适当设置--no-sandbox、--disable-dev-shm-usage这类参数。7. 踩坑实录我在真实项目里反复遇到的问题与最终解法所有框架原理说完了最后分享几个我在实际搭建这套框架时反复踩过的坑。这些坑非常具体希望你能绕开。7.1 显式等待和隐式等待同时使用会导致等待时间加倍一开始我在BasePage里用WebDriverWait做显式等待又在conftest里设置了implicitly_wait(5)结果某些用例跑得极慢失败时等到怀疑人生。原因很隐晦Selenium官方文档明确警告过显式等待和隐式等待不要混用混用之后等待时间不是取最大而是相加。比如find_element本身要等5秒WebDriverWait还要等10秒最坏情况下等待时长变成15秒。解决方法是去掉全局的implicitly_wait所有等待都用显式方式。或者反过来全用隐式等待但那会牺牲定位精确性。我最终选择了全显式等待加统一封装的方案所有find_element内部都走WebDriverWait行为一致且可控。7.2 iframe和Shadow DOM元素定位的大坑很多人写脚本时遇到明明元素在页面上就是定位不到的情况十有八九是遇到了iframe。页面里嵌了一个iframe网页里的元素在另一个文档里直接通过主文档的DOM是找不到的。处理办法是先切进去from selenium.webdriver.support import expected_conditions as EC # 切换到iframe iframe driver.find_element(By.XPATH, //iframe[idmain-frame]) driver.switch_to.frame(iframe) # 操作完再切回来 driver.switch_to.default_content()另一个类似场景是Shadow DOM处理方式是在find_element里使用ShadowRoot对象或者干脆用JS执行器。这块代码相对复杂实际项目中遇到的比例也比iframe小但如果你的被测系统用了某些现代前端组件库就得留意了。7.3 元素覆盖、不可点击click()却总是报错页面加载好了元素也可见了点击时还是报ElementClickInterceptedException。这大概率是因为页面上有其他元素盖住了目标元素常见于弹窗遮罩、导航栏、Cookie提示条等。Selenium认为元素被拦截就会拒绝执行点击。我的常规处理是如果能确认是弹窗遮挡就先关闭弹窗再点击如果是组件本身的可点击区域有偏移可以用execute_script强制点击element self.driver.find_element(*locator) self.driver.execute_script(arguments[0].click();, element)这里必须说明用JS点击等于跳过Selenium的点击模拟不校验元素是否可交互所以只应该用在明确知道这是前端实现差异造成的误拦截的情况不能遇到点击失败就无脑JS点击。否则会掩盖真实的交互问题。7.4 浏览器没有正确关闭导致环境中积累一堆僵尸进程这个问题在Windows尤其常见。用例执行中断、强制终止pytest进程时浏览器和驱动进程可能残留时间长了机器越来越卡CI环境的资源被吃光。解决思路两句话conftest里一定要用yield包裹driver.quit()确保清理动作被执行。同时可以写一个清理脚本在每次任务开始前杀掉残留进程。最稳妥的做法是把浏览器运行模式改为无头模式无头模式不打开物理窗口对环境的污染小得多。7.5 断言条件太脆弱文案变化导致用例大面积误报这一点前面提过这里再展开。很多前端页面在空数据、加载中、加载完成等状态下文案展示都不一样。如果你断言的文案是暂无数据结果某个环境里显示暂时没有数据用例直接误报。更稳健的做法是优先使用结构性特征做断言比如元素是否存在、数量是否符合预期、某个属性值是否变化。只有找不到任何结构特征时才退回到文案断言。文案断言一定要用包含而不是完全相等给前端的文案调整留一些余地。7.6 测试数据污染多个用例之间数据互相影响举个例子注册用例注册了一个账号删除用例又把同一个账号删掉了两个用例如果同时跑或者顺序变化就会互相干扰。我的建议是每个用例的数据尽量独立用例之间不做任何顺序依赖。测试数据在setup中准备在teardown中清理。依赖顺序的用例不是不能写但UI自动化中这种写法非常容易引发连锁失败——前一个挂了后面的全部跟着挂。你要想让框架稳定第一准则就是保证用例彼此独立、可单跑。7.7 无头模式与正常模式行为不一致同一套用例本地开浏览器跑全绿CI里无头模式跑就各种挂。原因主要是无头模式下的资源加载策略不同某些页面在无头模式下不会渲染某些元素。我的调整思路是不要默认无头模式而是通过配置去切换。本地开发用有头模式方便调试CI流水线再用无头模式。遇到有头正常、无头挂了的用例优先排查页面渲染是不是依赖了某些窗口尺寸或媒体查询相关逻辑给无头模式设置固定的window-size参数通常能解决一大部分。8. 我个人的一点体会和后续扩展方向这套框架从零开始搭到现在能稳定跑中间断断续续花了两三周时间。回头去看最重要的不是代码怎么写而是想清楚每一层要承担什么职责。定位器别散落、用例别依赖顺序、等待别混用、失败别只截图不保留日志这些规矩只要守住框架的维护成本就能压得很低。最后分享一个我在跑框架时的小技巧每次用例执行结束把执行耗时、通过率、失败用例列表写进一个历史文件每周看一眼趋势。如果通过率从99%掉到95%多半是前端改了哪里而不是测试代码坏了。这个趋势往往比单次报告更能说明项目的健康度。下一步我打算在这个框架基础上做两件事一是把接口测试也纳入进来形成接口加UI的分层测试机制二是研究一下并行执行把回归时间从四十分钟压缩到十五分钟以内。这两个方向等有实际结果了我再单独写文章分享。
返回列表