从零搭建Playwright+pytest Web UI自动化测试框架

发布时间:2026/10/18 18:12:20
从零搭建Playwright+pytest Web UI自动化测试框架 1. 从零搭建一套能落地的Web UI自动化框架先想清楚这几件事这几年做Web UI自动化绕不开一个名字叫Playwright。可能有人还在纠结Selenium是不是江湖地位更稳但我的结论很明确如果是新项目直接选Playwright pytest这套组合省心程度不是一点半点。Playwright自带一套非常完整的浏览器自动化能力pytest又是Python生态里最成熟、插件最丰富的测试框架两者一拼几乎覆盖了用例编写、执行、断言、报告、并发、持续集成里会遇到的所有常规需求。我实际用这套框架跑了几个项目的回归测试从登录、购物流程到后台管理系统的权限校验稳定性和可维护性都比之前用Selenium unittest的旧方案好太多。最直观的感受是Playwright的自动等待和网络控制能力把过去大量用来处理“元素找不到”“等待时间不够”的脏活累活给摘掉了pytest又把用例的组织、数据驱动、失败重跑、报告输出这些事情收拾得明明白白。说白了一句话框架搭得好的时候测试工程师大部分精力花在业务场景分析上而不是跟浏览器和断言较劲。这篇文章不是那种放一堆代码然后让你“照着抄就行”的教程。我会把框架从零到能跑通、能复用、能上CI的完整过程拆开来讲包括每个设计选择背后的原因、实际踩坑的细节、以及一些网上资料不会写的小技巧。不管你是刚入门自动化测试的新手还是正在改造旧框架的从业者只要你能看懂一点Python就能跟着把这套东西落地到自己的项目里。1.1 为什么是Playwright而不是Selenium先说最扎心的一点Selenium的WebDriver虽然兼容性广但它本质上是在浏览器外面起一个驱动进程再通过协议去控制浏览器。这套架构带来的问题就是慢、不稳定而且不同版本的驱动和浏览器版本之间经常闹脾气。我在老项目里最头疼的就是环境问题代码本身没毛病跑起来却因为Chrome升级导致WebDriver版本对不上回归一次得先花半小时清理环境。Playwright完全不同。它直接把浏览器控制能力做进了自己的工具包里通过CDP协议跟浏览器通信不需要额外装驱动。这意味着什么一是安装配置极简二是执行速度确实更快三是它提供了一套非常强的自动等待机制。比如你在页面上点一个按钮Playwright会自动等待按钮出现在DOM里、可见、可点击再执行点击整个过程不需要你手动写sleep或者显式等待。写过Selenium的人应该都懂这类等待逻辑是测试脚本里最烦人、最容易脆弱的代码。还有一个非常关键的点Playwright支持多浏览器包括Chromium、Firefox和WebKit而且同一套代码可以在无头和有头模式之间自由切换。之前用Selenium做跨浏览器测试要分别装驱动、分别调试现在一个API搞定。对于需要兼容多种浏览器的Web项目这个优势是决定性的。1.2 pytest在测试框架里的角色pytest本身不是为UI自动化设计的它是通用的Python测试框架。但正因为通用它的fixture机制、参数化功能、插件生态恰好能把UI测试里那些重复的“启动浏览器、打开页面、登录、清理数据”流程给抽象出来。fixture可以理解成一个“带准备和清理的依赖注入器”。比如我可以定义一个session级别的fixture负责启动一个浏览器实例整个测试会话复用再定义一个function级别的fixture负责每个用例都打开新的页面上下文。这样做的好处是浏览器进程不反复启动页面上下文又是隔离的用例之间互相不串数据。pytest还支持参数化同一个用例可以传多组数据进去跑。这个在UI测试里极其实用。比如登录用例我可以把正常账号、错误密码、空用户名、锁定账号这些case都写成一个用例函数用参数列表驱动一份代码覆盖N个场景。配合pytest的插件还能做失败重试、生成Allure报告、并行执行等等。总而言之pytest是整个框架的骨架Playwright只是被它调度和装配起来的执行引擎。2. 框架整体设计与目录结构框架搭得漂不漂亮不是看代码多花哨而是看新增一条用例时你要动几个文件。我见过很多“自动化测试框架”一个文件几千行用例之间大量复制粘贴改一个页面元素要在十几个用例里找。这种框架跑起来越快维护成本越高最后只能推到重来。所以设计的第一步就是定目录结构让关注点分离。2.1 模块划分不是把所有代码堆进test用例我推荐的目录结构大概长这样project/ ├── config/ # 配置文件与环境定义 │ ├── settings.py │ └── env_config.yaml ├── pages/ # PageObject层每个页面一个文件 │ ├── base_page.py │ ├── login_page.py │ └── dashboard_page.py ├── test_cases/ # 用例层只写业务场景和断言 │ ├── conftest.py │ └── test_login.py ├── utils/ # 通用工具比如截图、日志、数据处理 │ ├── logger.py │ └── screenshot.py ├── reports/ # 测试报告与运行日志 ├── requirements.txt └── pytest.ini很多初学者喜欢把用例直接写在操作逻辑里比如打开浏览器、输入账号、点登录、断言全在同一个函数里。看起来简单但一旦页面UI变化你就要把所有用例翻出来改一遍。PageObject模式的核心思路是每个页面封装成一个类类里面只描述这个页面的元素定位和页面操作行为用例里只表达“用户干了什么”“结果应该是什么”。登录页就不关心其它页面Dashboard页就不需要知道登录按钮在哪。BasePage这个类就更基础了一般会封装Playwright的通用操作比如点击、填写、等待、拖动、滚动。其它页面类继承BasePage只写自己的专属元素和方法。这样分层之后底层库换版本、页面结构调整、业务场景新增影响面都被限制在一个很小的范围内。2.2 配置管理与环境切换Web自动化最常遇到的环境问题就是测试环境、预发环境、生产环境URL不一样账号不一样可能还有一些功能开关也不一样。如果这些配置硬编码在用例文件里那换环境就得改代码非常危险。我的做法是用一个yaml文件维护不同环境的配置再在程序里根据环境变量读取对应数据。settings.py负责加载配置整个框架里所有需要URL、账号、密码的地方都从配置中心拿绝不手写在用例里。这样只需要在运行命令时指定测试环境比如export TEST_ENVstaging pytest test_cases/test_login.py代码里就能通过os.getenv拿到“staging”对应的配置字典。千万别小看这一步。曾经我在一个项目里就是因为把测试环境的域名硬编码到了脚本里结果预发环境验证的时候全部用例跑挂排查了半天才发现是配置没切换。配置中心化之后这种低级问题再也没有出现过。2.3 核心公共组件PageObject与BasePage我再展开说一下BasePage的封装思路。BasePage里会注入Playwright的Page对象然后提供几个最常用的方法比如fill_text(selector, text)清空输入框再填入文字click_button(selector)点击前等待元素可点击get_text(selector)获取元素文本用于断言wait_for_element(selector, timeout)显式等待某个元素出现take_screenshot(name)失败时截图留存这些方法内部都用Playwright的locator语法写定位策略统一。为什么这么做因为直接暴露原始Page对象给用例容易出现“各种写法五花八门”的情况。比如同样是定位一个按钮有人用page.query_selector有人用page.locator还有人用XPath代码风格完全失控。把公共操作收拢到BasePage之后编写用例的人只需要记住几个方法代码可读性一下子上去了。3. 关键依赖安装与pytest插件实战框架的代码结构不是全部。真正决定体验的是依赖选型和插件配置。这里我会说清楚我选了哪些库、每个库解决什么问题、有哪些坑必须避开。3.1 安装清单与版本选择我的requirements.txt长这样playwright1.45.0 pytest8.2.2 pytest-playwright0.5.1 pytest-xdist3.6.1 pytest-rerunfailures14.0 allure-pytest2.13.5 PyYAML6.0.1需要解释一下几个关键项。pytest-playwright是官方提供的pytest集成插件装上之后它会自动注册几个fixture比如browser、context、page你不需要自己写浏览器启动逻辑。这个插件大大降低了入门门槛但如果你有高度定制需求它也不拦着你覆盖默认fixture。pytest-xdist用来并行跑用例多核机器上速度提升非常明显。pytest-rerunfailures负责失败用例自动重试UI自动化里有很多偶发性的弹窗、网络抖动、动画未结束问题先重试一次能过滤掉大部分非真正的功能bug。allure-pytest则是生成那种带步骤、截图、日志的漂亮报告。PyYAML用来解析配置文件。还有一个必须做的步骤安装Playwright之后记得执行一次浏览器二进制文件的下载命令playwright install不执行的话运行时它会直接提示找不到浏览器。这里有个坑如果你在容器或CI环境里跑还需要额外安装系统依赖库否则浏览器可能启动不了。用下面的命令安装系统依赖playwright install-deps千万注意这个命令在某些精简版Linux镜像上会缺一些常见库比如libX11、libnss3虽然install-deps会自动装但公司内网环境如果限制外网你可能需要把这些包提前打底。否则跑起来全是“浏览器启动失败”的报错。3.2 在pytest中配置Playwright的fixture即使靠pytest-playwright可以拿到现成的page fixture我还是建议你在项目的conftest.py里做一层自定义封装。原因很简单现成fixture确实方便但它默认是全自动的你不好插入自己的前后置逻辑比如登录、初始化数据库、记录日志。一个比较稳的conftest.py写法是import pytest from playwright.sync_api import sync_playwright from config.settings import load_config pytest.fixture(scopesession) def env_config(): return load_config() pytest.fixture(scopesession) def browser(env_config): with sync_playwright() as p: browser p.chromium.launch( headlessenv_config[headless], args[--disable-blink-featuresAutomationControlled] ) yield browser browser.close() pytest.fixture() def page(browser): context browser.new_context( viewport{width: 1920, height: 1080}, ignore_https_errorsTrue ) page context.new_page() page.set_default_timeout(15000) yield page context.close()scopesession的browser只启动一次所有用例复用同一个浏览器实例但每个用例创建独立的context和page相当于一个隔离的用户会话。这么做的好处是速度快、隔离好。这里注意set_default_timeout(15000)把默认超时设置成15秒很多元素加载慢的场景都够用又不会让失败用例等太久。如果你有登录态的需求比如大部分用例需要登录后才能操作我建议建一个authed_page fixture在page fixture的基础上执行登录操作然后yield出来。这样绝大多数用例都不需要关心登录逻辑只有未登录场景的用例才单独用page fixture。3.3 用pytest-playwright还是自己封装我的取舍这个问题我在团队里被问过很多次。pytest-playwright提供的fixture确实开箱即用但它太“官方模板化”了不太适合复杂业务。比如你要在失败时自动截图、附加视频、打印请求日志、跳过登录步骤这些都需要自己写逻辑。所以我最终的方案是引用pytest-playwright作为依赖但只把它当底层库用fixture全在自己的conftest里重写。这样做的好处是整个框架的执行逻辑完全透明可控坏处是你要多写一点代码。但对于一个要长期维护的框架来说可控性比省几行代码重要得多。另外pytest-playwright版本升级时你自定义的fixture不受影响不会因为插件内部变化导致项目挂掉。我的建议是新手可以先直接用官方fixture跑通流程等要对框架做定制的时候再逐步替换成自己的实现。4. 实操从浏览器启动到第一个可复用用例光说不练假把式。这一节我会完整走一遍从浏览器启动到跑通业务用例的过程包括一些运行命令、参数说明和现场记录。4.1 第一步把最基础的登录场景跑通假设我们要测一个后台管理系统的登录。登录页有用户名输入框、密码输入框、登录按钮登录成功后页面上会显示“欢迎回来”的提示。最简单的用例长这样# test_cases/test_login.py from pages.login_page import LoginPage from pages.dashboard_page import DashboardPage def test_login_with_valid_credentials(page, env_config): login_page LoginPage(page) login_page.go_to(env_config[base_url]) login_page.login(env_config[admin_user], env_config[admin_password]) dashboard_page DashboardPage(page) assert dashboard_page.get_welcome_text() 欢迎回来注意用例里完全没有出现任何元素定位或浏览器的细节登录动作被封装在LoginPage的login方法中断言也通过DashboardPage暴露的方法来做。这就是PageObject的好处用例读起来像业务描述一样。LoginPage的简化实现里需要注意一个细节登录成功后页面会经过一个跳转和加载过程如果立刻去定位Dashboard页的元素可能会扑空。所以我通常在login方法最后会调用page.wait_for_url或者等待某一个登录后独有的元素出现这样再返回的时候页面状态就是稳定的。不要试图用一个固定的time.sleep(5)糊弄加载慢的时候5秒不够快的时候浪费时间Playwright的自动等待就是为这个场景设计的。4.2 第二步参数化驱动多账号多环境数据UI测试里登录场景最值得参数化。比如正确的用户名密码登录成功错误的密码提示“用户名或密码错误”用户名为空提示“请输入用户名”密码为空提示“请输入密码”账号被锁定提示“账号已锁定”用pytest的pytest.mark.parametrize可以一次性贴上这些数据一个用例函数跑五遍。pytest.mark.parametrize(username,password,expected_message, [ (admin, correct_password, 登录成功), (admin, wrong_password, 用户名或密码错误), (, 123456, 请输入用户名), (admin, , 请输入密码), (locked_user, 123456, 账号已锁定), ]) def test_login_scenarios(page, env_config, username, password, expected_message): login_page LoginPage(page) login_page.go_to(env_config[base_url]) login_page.login(username, password) assert login_page.get_message_text() expected_message这种写法比复制粘贴五个用例要干净得多。但这里有坑如果前面一个参数组合已经登录成功页面上可能已经处于登录态下一个参数组合就要先清除cookie或者重新打开一个新的context。所以我在page fixture里每次创建新context每个用例天然是干净的浏览器环境避免用例间数据污染。4.3 第三步失败自动截图与视频留存UI自动化用例失败后最痛苦的事情是你看不到它失败时候页面长什么样。所以截图和视频录制是必备的。我习惯在conftest.py里做一个自动截图的操作pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: page item.funcargs.get(page) if page: timestamp time.strftime(%Y%m%d_%H%M%S) page.screenshot(pathfreports/screenshots/{item.name}_{timestamp}.png)这段代码的作用是不管用例失败只要执行到call阶段就自动从fixture里拿page对象截图保存到固定目录。注意这里要确保目录存在可以在conftest里用os.makedirs(..., exist_okTrue)提前创建。如果你还想保留视频Playwright的context是可以开启record_video_dir的。在创建context的时候传入record_video_dirreports/videos浏览器操作过程会被录制成webm文件。这个功能在调试复杂问题时特别管用它能回放整个过程定位到底是哪一步交互出了问题。4.4 第四步与CI流水线集成的几个小坑框架写完之后最终目标通常是挂到流水线上自动跑。这里我要提到三个常见坑。第一个是无头模式。CI环境里没有显示器Playwright要运行在headless模式下也就是不打开浏览器窗口。通常在配置环境里切换而不是硬编码。比如本地调试用有头模式CI地用无头模式。第二个是屏幕分辨率。很多Web页面的布局和按钮的可见性跟视口尺寸强相关。我在CI里把浏览器视口设置为1920x1080同时在具体用例里不依赖滚动可见性但还是遇到过一个“按钮不在视口内点击失败”的问题最后用locator.scroll_into_view_if_needed()解决。这个API是Playwright专门为这类场景准备的。第三个是重试与并行。CI流水线里我通常只会让pytest-xdist并行到2到4个进程因为UI自动化对CPU和内存的占用是真的高盲目堆worker数量会导致资源争抢、用例大量超时。失败重试建议设置在1次不要太多否则会掩盖真实问题排查起来也困难。5. 常见问题与排查技巧实录最后这部分我把这几个月里摸爬滚打遇到的问题整理成速查表每个问题的排查思路和解决方案都是我实际跑过、验证过的。5.1 元素明明存在点击却报timeout这个坑我在早期频繁遇到。现象是定位器能找到元素点击的时候却一直超时。后来我发现原因有三类第一元素被遮住了比如有遮罩层或者弹窗在页面上方第二元素其实在DOM里但不可见比如父容器有隐藏样式第三元素处于“正在变化”的状态每次定位都不一样。排查方法很简单在headless模式下加一行page.pause()或者打开headed模式仔细观察。更高效的做法是用Playwright的inspector工具设置环境变量PWDEBUG1然后运行用例它会自动打开一个调试窗口帮你看到元素匹配情况和操作过程。解决手段如果元素可能被遮挡优先使用element.click(forceTrue)但要谨慎它会跳过可操作性检查可能掩盖真实问题更合理的是用element.scroll_into_view_if_needed()把元素滚进视口再执行点击。5.2 环境切换导致用例串数据我们有个项目在测试环境和预发环境之间共用一套代码结果某些用例在预发环境执行时会用到测试环境的过期数据导致断言失败。排查时发现用例在使用数据前没有重新读取配置有些常量还是模块加载时缓存的。解决方案强制所有环境相关数据在fixture阶段动态读取不要定义为模块级常量。还有一点登录后的会话状态cookie默认是绑定域名的环境切换后旧的context不再使用避免串数据。5.3 并发执行时浏览器资源占用过高用pytest-xdist并行跑的时候每个worker都有自己独立的浏览器进程。4个worker跑起来加上系统本身的服务内存一下子吃紧。如果并发数量设置不当还会出现“浏览器启动失败超时”之类的问题。我的调整方案限制并发数最多为物理核数的一半同时在browser启动参数里加上--disable-dev-shm-usage。这个参数在容器环境里尤其重要因为默认的/dev/shm分区太小多浏览器进程时会直接崩掉。另外每个worker跑完用例后确保context和browser都被关闭不要留在后台占用资源。5.4 报告与日志为什么总是对不上时间我刚开始用pytest-xdist并行跑的时候看Allure报告里的用例执行时间特别乱。排查后发现是多个worker同时写日志文件行与行的时间顺序被打乱了。这个问题不解决排查问题的时候非常痛苦。我的做法是日志文件名里加入worker标识符/reports/logs/worker-{worker_id}.log这样每个进程的日志独立成文件看的时候按用例名称检索。Allure报告本身不依赖时间线它按用例组织所以报告里的执行顺序是准的但日志文件要分开。另外有时报错信息里包含时间戳如果系统时钟不同步也会造成误导我后来统一在容器启动时用同一个时钟源这个坑才算彻底解决。5.5 自动等待与隐式等待的冲突有些团队的旧代码会习惯性在脚本开头写上page.set_default_timeout或者其它等待逻辑。但Playwright其实推荐的做法是依赖locator内部自动等待不要手动加全局sleep。我自己在实际项目中把显式sleep全部清掉统一用locator的等待机制。结果是执行速度快了接近三分之一用例稳定性不降反升。记住一个原则能用expect(locator).to_be_visible()写明条件就不要用sleep碰运气。说实话我很少把一套框架从头到尾讲得这么细因为实操中的体验只有亲手撞过墙才知道。Playwright pytest这套组合让我最满意的地方就是它把“写用例”这件事重新变回了“描述业务场景”而不是“伺候浏览器”。如果你正准备搭框架希望这一套拆解能帮你少走一些我曾经走过的弯路。真正动手跑起来之后遇到奇怪的问题先想想是不是等待没用好、配置没隔离、并发调太高大部分坑都能绕过去。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询