十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

还在用Selenium?试试Playwright:自动等待与跨浏览器自动化实战

还在用Selenium?试试Playwright:自动等待与跨浏览器自动化实战 还在用Selenium写自动化测试的同学我劝你尽早看看Playwright。这个来自微软的浏览器自动化工具我用了不到一年最直观的感受就是以前写一套用例要调半天的等待时间、处理各种弹窗和iframe现在基本可以“无脑”写了。更关键的是它自带一套断言重试机制元素没出现就等出现就继续根本不用你手动sleep。如果你平时做Web自动化测试、爬虫、或者想用脚本代替人工重复操作浏览器这篇内容会比较适合你。我不打算做那种官方文档翻译而是把我从安装到落地、从踩坑到解决的全过程记录下来尽量讲清楚“为什么这么用”而不是只给你看API。1. 为什么我从Selenium切换到Playwright一次反直觉的选择在Playwright出现之前浏览器自动化基本被Selenium垄断。我团队里早期做Web自动化也用的是Selenium兼容性没问题但用久了会攒下一堆让人烦躁的小毛病。比如元素定位时经常会遇到“元素不可交互”的报错明明页面已经加载出来了但脚本就是点不到再比如网络慢一点、前端框架还在渲染的时候必须硬编码一段time.sleep(3)来等等久了脚本执行时间暴涨等短了分分钟报错。这套老路子能跑通但维护成本很高。1.1 自动等待机制它比你想的更智能Playwright把“等待”这个事做成了内置机制而不是让你自己调。它的核心逻辑是“动作前自动等待”——你调用click、fill这类操作时Playwright会先轮询检查这个元素是否可见、是否稳定、是否可交互如果条件不满足就一直等直到超时。这背后其实是它对DOM状态做了一套非常细致的判断包括元素是否附着在文档里、是否被其他元素遮挡、是否处于动画状态等等。这意味着你完全不需要去猜测一个按钮要等多久才能点脚本会在元素真正可操作的那一刻自动继续。我记得有一次跑一个非常慢的后台管理页面前端是Vue框架表格数据加载依赖好几个异步接口。用Selenium写的用例稳定运行几周后突然开始随机失败排查下来是因为后端接口变慢了之前的sleep(2)不够用了。换成Playwright以后同样场景下click和fill操作就不再依赖固定的等待时间它会等元素出现再操作用例马上稳了下来。1.2 一套API跑三种浏览器引擎还顺便解决了WebDriver的环境问题Selenium需要为每个浏览器单独下载对应版本的WebDriver浏览器一升级WebDriver就可能失效这是很多测试环境维护噩梦的来源。Playwright不同它走的是CDPChrome DevTools Protocol或自家封装协议安装时直接用命令行把对应浏览器内核下载到本地不需要额外安装驱动也不受浏览器自动升级的影响。更有意思的是同一个API可以无缝跑Chromium、Firefox和WebKit也就是Safari的引擎。写一遍用例跑到三个浏览器里验证这在做兼容性测试时效率提升非常明显。尤其对于前端团队有些CSS或布局问题可能只在WebKit里出现以前你得专门配一台Mac现在用Playwright在Linux上也能调用WebKit内核做验证。1.3 对比Selenium到底哪些差异值得你在意简单整理下我实际使用中感受到的差异对比维度SeleniumPlaywright等待策略需要显式等待或强制等待自动等待几乎不用手写等待逻辑浏览器驱动需要手动下载对应版本WebDriver命令一键安装自带内核多标签/多页面处理起来比较别扭原生支持多page切换方便iframe需要切换context容易迷失直接用frame locator定位逻辑清晰网络拦截需要借助第三方代理工具内置route接口可以mock请求录制脚本有IDE但生成代码较笨重录制器生成的代码结构更干净直接能改对于已经习惯了Selenium的测试工程师迁移到Playwright需要调整的就是写代码的心智模型不再是“找元素-操作-等待”三段式而是“定位器-动作-自动等待”一体化。开始可能不习惯但用两周左右基本就能适应。2. 环境准备与第一次启动浏览器比想象中省事Playwright的安装流程相当简单但有几个细节值得说清楚避免新手在版本兼容性上浪费功夫。2.1 Python和Node.js版本怎么选Playwright同时提供Python和Node.js版本功能基本对齐。我个人的建议是如果你的测试脚本主要用于自动化测试或爬虫且团队现有的技术栈是Python那就选Python版本写起来快生态里也好和pytest、allure这些工具集成。如果你本身是前端工程师想用自动化做端到端测试或者要深度利用Playwright Test Runner的测试报告、断言库、并行执行这些能力那更推荐Node.js版本。我团队最终选的是Python版本因为我们的接口自动化脚本、数据处理脚本都跑在Python环境里统一技术栈以后好维护。但这个选择不绝对两个版本我都简单跑过核心API的命名和用法几乎一致学会了其中一个切换过去成本很低。安装命令非常简单pip install playwright之后还需要执行一步playwright install来下载浏览器内核。这一步是新手容易忽略的——只装库不装浏览器运行时直接报错。而且因为下载源在国外部分地区可能会遇到下载超时可以设置镜像环境变量来解决这一步我建议直接配好。pip install playwright # 安装chromium内核默认会下载到用户目录 playwright install chromium2.2 同步API和异步API第一次该用哪个这是新手最容易纠结的点。Playwright在Python里同时提供了sync_playwright和async_playwright两个入口。简单来说同步API适合大多数日常测试脚本写起来逻辑清晰不容易乱异步API适合要并发跑大量任务、或者要集成到已有异步代码里的场景。我的建议是如果只是写常规的自动化测试脚本直接用同步API别一上来就上异步异步的await会散布在代码各处排查问题的时候多一层心智负担。异步API的优势在于并发比如你有100个URL需要批量去检查页面是否正常用异步把任务并发出去整体耗时能缩短好几倍这种场景才值得用异步。下面是一个最小可运行的同步脚本启动浏览器、打开页面、输出标题然后关闭from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://example.com) print(page.title()) browser.close()2.3 启动浏览器的几个参数日常都怎么配browser.launch()里有几个参数很常用我一般会重点关注这几个headlessFalse如果你想看脚本实际在浏览器里的操作过程就设为False默认是True即无头模式。slow_mo500给每一步操作加一个500毫秒的延迟适合调试和演示能看到操作轨迹。args[--start-maximized]给Chromium内核传启动参数比如启动就最大化窗口。channelchrome如果你想用本机安装的Chrome而不是下载的Chromium内核可以用channel参数指定。要注意的是headlessTrue的性能优势很明显在CI环境跑测试时基本都是无头模式但在调试初期我建议开有头模式因为能肉眼看到脚本到底在操作什么出错时也更容易直观理解。2.4 理解Browser、Context和Page三层结构很多刚接触Playwright的人会被Browser、Context、Page这仨概念绕晕。我用一个生活化的类比来解释Browser就是整个浏览器程序相当于一个独立的厨房Context是浏览器里的一个“独立会话”相当于一个隔间不同Context之间互不共享Cookie、缓存和登录状态Page就是Context里的一个标签页相当于灶台上的一口锅。这个结构最大的好处是你可以很方便地做“会话隔离”。比如你要测试多账号登录的场景不需要反复登录退出了直接创建两个Context一个操作账号A一个操作账号B互不干扰。这也是Playwright相对Selenium的一个明显优势Selenium里每开一个新窗口Cookies是共享的要隔离得自己多写很多代码。with sync_playwright() as p: browser p.chromium.launch() # 创建两个完全隔离的会话context context_a browser.new_context() context_b browser.new_context() page_a context_a.new_page() page_b context_b.new_page() page_a.goto(https://example.com/login) page_b.goto(https://example.com/register)3. 页面交互的核心定位器、动作与状态断言页面交互是自动化测试的核心环节Playwright把“找元素”和“操作元素”封装成了一个整体叫做定位器Locator。这个设计思路值得单独拿出来讲。3.1 定位器Locator为什么比传统找元素方式更可靠在Selenium里通常是用find_element_by_id、find_element_by_xpath这类方式先拿到元素然后再调用click、send_keys。Playwright则是用page.locator()创建定位器并且这个定位器不是一次性的快照而是每次操作时都会重新去页面里查询一次。这意味着即使页面上元素被重新渲染了只要选择器依然有效定位器还是能正常工作不会出现“元素引用过期”的问题。它还加持了两大特性一是自动等待二是严格模式。严格模式是Playwright默认开启的意思是如果定位器匹配到了多个元素操作时会直接报错提示你选择器不够具体。这在早期能帮你发现很多潜在问题避免因为页面上有多个相似元素而操作了错误的目标。拿最常见的登录框举例定位和填写的代码非常简洁page.goto(https://example.com/login) # 定位器会自动等待输入框可交互 page.locator(#username).fill(test_user) page.locator(input[namepassword]).fill(test_pass) page.locator(button[typesubmit]).click()3.2 常用动作与表单操作从点击到文件上传Playwright的常用动作API很全基本覆盖了用户在浏览器里能做的所有操作。点击、双击、右键、悬停、拖拽、滚动到可见区域这些都有对应方法。表单操作里的fill、press_sequentially逐字输入、select_option下拉选择、set_input_files文件上传也都有而且设计得很顺手。这里我说两个经常有人问的场景第一个是输入框的赋值。一般情况下用fill就够了它会把输入框里的内容清空再填入新值。但如果你要输入的内容很长或者你想模拟真人打字的效果可以用press_sequentially它会一个字符一个字符地输入。第二个是文件上传。不少人在自动化里遇到文件上传就头大因为会弹出一个系统级的文件选择对话框Selenium处理这个很绕。Playwright的做法是直接绕过系统对话框用set_input_files把文件路径绑定到input typefile元素上非常清爽# 单文件上传 page.locator(input[typefile]).set_input_files(/path/to/file.pdf) # 多文件上传 page.locator(input[typefile]).set_input_files([/path/a.pdf, /path/b.pdf])3.3 断言为什么重要不再写一堆if判断去等结果以前写自动化测试想验证一个操作是否成功经常要写一个WebDriverWait循环去轮询页面文本再判断是否符合预期。Playwright把断言做进了测试库提供了expect这种自带重试机制的断言方式。什么意思呢就是断言不通过时它不会立刻失败而是一直轮询检查直到条件满足为止。最常见的文本断言长这样from playwright.sync_api import expect # 点击登录按钮后期望页面出现欢迎回来文本 expect(page.locator(.welcome)).to_have_text(欢迎回来)如果页面渲染速度较慢这个断言最长会等待默认的5秒超时时间只要在5秒内出现“欢迎回来”就算通过。这个重试机制是把“等待”和“断言”结合在了一起大大减少了脚本编写时的焦虑感。3.4 处理多页面和iframe不再那么头疼多标签页操作和iframe跨域处理是Web自动化里两个经典难题。Selenium处理多标签页要driver.window_handles切换处理iframe要switch_to.frame代码容易绕晕。Playwright处理这两类场景的思路很直接多标签页场景可以用context.expect_page()来监听新页面打开事件with context.expect_page() as new_page_info: page.locator(a[target_blank]).click() new_page new_page_info.value new_page.wait_for_load_state() print(new_page.title())iframe场景直接用frame_locator在iframe内部继续定位元素不需要来回切换上下文# 直接在iframe里面定位输入框并填入内容 page.frame_locator(iframe[data-testidlogin-frame]).locator(#username).fill(test_user)这个设计让代码的可读性变得非常好逻辑是一层层嵌套进去的而不是在多个上下文之间来回跳转。4. 进阶玩法网络拦截、认证复用与移动端模拟Playwright真正强大的地方在于它不止能做常规的点击和输入还能很优雅地处理网络请求、认证状态和移动端适配。这些能力在解决实际复杂问题时价值非常明显。4.1 用route接口拦截和修改网络请求page.route()可以拦截页面发起的网络请求然后选择继续、中止或者伪造返回数据。这在很多场景下都很有用测试弱网环境让某些接口延迟返回观察前端loading状态是否正确。屏蔽干扰资源把统计脚本、广告脚本直接中止让测试更稳定。构造异常接口某接口返回500验证前端有没有正确的错误提示。Mock后端数据后端还没开发完时前端可以先按约定格式mock数据继续测试。一个常用的mock接口返回案例def mock_api_response(route): # 伪造一个接口的JSON返回 route.fulfill( status200, content_typeapplication/json, body{code: 0, data: {user: mock_user, roles: [admin]}} ) page.route(**/api/user/info, mock_api_response) page.goto(https://example.com/dashboard)这里还要提一个“和爬虫结合”的方向。很多人用Playwright来做动态页面的数据采集因为现在大量网站是前端渲染普通HTTP请求拿不到完整数据。用Playwright打开页面、等待渲染完成后提取内容是这类场景的标准解法。但如果目标网站有比较严格的反爬策略可能会检测到这是自动化浏览器。这里我不展开讲那些“过检测”的黑科技只提醒一句page.route()可以用于绕过一些静态资源加载降低被识别概率但一定要遵守目标网站的协议和法律法规别做越界的事。4.2 Cookie和登录状态的复用解决频繁登录的痛点在自动化测试里频繁登录是一个很烦人的问题。很多系统的登录流程都带图形验证码、短信验证码让自动化脚本非常难处理。Playwright提供了一个思路把登录后的存储状态Cookies和LocalStorage保存下来以后的测试脚本直接加载免登录。具体方法是先用脚本手动完成一次登录然后调用context.storage_state()把状态存成JSON文件后续每次新开Context时直接传入这个文件# 保存登录状态 context browser.new_context() page context.new_page() page.goto(https://example.com/login) # 在这里做登录操作登录成功后运行下面这行 context.storage_state(pathlogin_state.json) context.close() # 复用登录状态 context browser.new_context(storage_statelogin_state.json) page context.new_page() page.goto(https://example.com/dashboard)这个方法在实际项目里帮我们省了大量时间。尤其是一些业务后台登录后的会话有效期很短加上复杂的验证流程如果每个脚本都要从头登录整个回归测试根本跑不动。用存储状态复用以后一条用例可以从“打开后台首页”直接开始节省了至少一半的执行时间。4.3 模拟移动端浏览器顺便做响应式验证Playwright支持通过device_descriptor来模拟各种移动设备包括iPhone、iPad、Pixel等。它不只是简单的改User-Agent而是连屏幕尺寸、触摸事件、设备缩放比、是否支持移动端特性都会一并切换。from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch() iphone p.devices[iPhone 13] context browser.new_context( **iphone, ) page context.new_page() page.goto(https://example.com)这个能力对做移动端H5页面测试特别有用不需要真机直接就能在电脑上模拟各种移动设备的浏览环境渲染效果和真机很接近。实际跑下来CSS兼容问题基本能暴露个七七八八真机适配时踩雷的概率大大降低。4.4 和AI结合的方向Playwright MCP与智能生成测试脚本最近AI自动化测试是个热门方向Playwright在这个领域其实有很好的结合点。一个是Playwright团队推出的MCP服务可以把浏览器的操作能力接入支持MCP协议的AI助手让AI模型像人一样控制浏览器去做验证。另一个是Codex这类AI编程助手配合Playwright通过自然语言描述测试场景自动产出可运行的测试脚本。我试过用AI辅助生成Playwright脚本体验是对于结构清晰、页面元素有明确语义的站点AI生成的脚本可用度挺高甚至能自动处理等待和断言但对于复杂的业务系统AI生成的定位器偶尔还是会失效这时需要人工微调。比较务实的用法是把AI当成“脚本草稿生成器”先让AI产出一个完整的流程脚本再在真实环境跑一遍把失败的选择器改对这样搭建用例的效率能翻倍。5. 几个最值得收藏的避坑经验从报错处理到设计建议用了这么久的Playwright遇到过的坑不少。有些问题如果不知道排查起来会非常痛苦。我挑几个最有代表性的记录在这里希望能帮大家少走弯路。5.1 “target closed”报错最常见也最头疼的问题很多刚上手Playwright的人都遇到过target closed这个报错字面意思是目标页面、页面上下文或浏览器已关闭。出现这个报错的原因很常见代码执行过程中页面因为某些原因被关闭了或者浏览器/Context被提前关闭了。最容易踩坑的场景是这样的在with sync_playwright() as p:的上下文里操作完某个页面后不小心调用了browser.close()但后续代码还在继续尝试操作之前的page就会报这个错。还有一种隐藏较深的场景是页面里的某个按钮触发了window.close()或者页面跳转导致旧的Document被销毁后续还尝试操作旧元素同样会出现这个报错。排查思路很简单报错信息里会有具体的操作堆栈先看是哪一行操作触发再看那个时间点页面是否被显式或隐式地关闭了。如果确认页面不该被关闭但依然报错可以尝试在操作前加上page.wait_for_load_state()确保页面跳转完成后再继续操作。5.2 输入框内容没清空、下拉选项对不上表单操作有讲究fill方法本身会先清空输入框再输入新内容所以一般不用手动清空。但有一个特殊情况如果输入框被前端框架接管了值绑定比如Vue的v-modelfill可能触发了input事件但没触发change事件导致提交时数据不对。解决办法是在fill之后再用dispatch_event显式触发change事件page.locator(#input).fill(new_value) page.locator(#input).dispatch_event(change)下拉选择也是一个常见困惑。select_option默认是按value来选择如果你手头只有文本内容需要传label参数。更要注意的是如果下拉框不是原生select元素而是自定义组件比如用div模拟的select_option是不生效的必须点击展开后用locator去精确点击列表项或者设置背后的隐藏输入框值再触发相应事件。5.3 下载文件时注意的问题别把下载路径丢了用Playwright处理文件下载时建议用expect_download()上下文管理器来捕获下载事件然后调用download.save_as()把文件保存到指定目录。这里有个容易踩的坑如果没有用save_as把下载文件持久化保存临时下载文件会在浏览器上下文关闭时被清理掉到时候再想找就找不到了。with page.expect_download() as download_info: page.locator(a[download]).click() download download_info.value download.save_as(/path/to/save/report.pdf)另外下载大文件时save_as是异步写磁盘的如果脚本很快就退出可能会发现文件没保存完整。稳妥的做法是在保存后手动检查文件大小是否大于0或者等几秒再让脚本结束。5.4 注意区分“可见”和“存在”这是断言失败的常见原因写断言时大家通常会用locator.is_visible()或者to_be_visible()来判断元素。但有个隐蔽的场景有些元素在DOM里存在而且尺寸不为0但在视觉上被其他元素覆盖或者被CSS属性如opacity:0隐藏。这种情况下is_visible()的判定结果可能不符合直觉。Playwright的自动等待在操作时会额外检查“是否可交互”不仅可见而且未被遮挡但断言时不一定做同样的检查。所以遇到“元素明明存在但断言不可见”的问题时优先看CSS样式是不是有覆盖或者透明度设置不要只从元素是否存在去排查。我的经验是断言文本内容用to_have_text()或to_contain_text()比单纯断言可见性要稳得多因为这更贴近业务逻辑——页面渲染出来就是给用户看内容的文本出现就意味着页面基本正常。5.5 测试数据准备和环境清理别忽略测试基建最后再说一点偏测试设计层面的建议。Playwright让写脚本变得容易但脚本写得再好如果测试数据一团糟CI跑起来照样天天红灯。在实际落地时建议建立一套独立的测试数据生成和清理机制每个测试用例跑完能自动清理自己造的数据。同时线上和测试环境的配置要隔离开别让测试脚本误操作了生产环境的数据。这些虽然不是Playwright本身的能力但在项目中使用Playwright做自动化时这些基建不打好工具的威力发挥不出来。6. 从脚本到工程如何把Playwright落地到团队项目里工具用熟了之后紧接着的问题就是怎么在一个团队里把Playwright真正落地让它成为自动化测试体系里稳定运转的一部分而不是某个同事电脑里的实验脚本。我们最终采用的是pytest结合Playwright的方案。pytest作为测试框架负责用例的组织、执行、跳过和报告Playwright负责浏览器操作。这套组合的好处是pytest的fixture机制可以很优雅地管理浏览器实例的创建和回收每个测试用例都可以声明自己需要哪种浏览器环境、是否需要登录状态代码非常干净。一个简单的fixture定义如下import pytest from playwright.sync_api import sync_playwright pytest.fixture(scopefunction) def page(): with sync_playwright() as p: browser p.chromium.launch() context browser.new_context( storage_statelogin_state.json, viewport{width: 1280, height: 720}, ) page context.new_page() yield page context.close() browser.close()这样定义好以后具体的测试用例只需要接收page参数直接操作即可框架会自动完成浏览器启动、状态加载和实例回收。用例数一多推荐把公共操作封装成页面对象Page Object每个页面类负责该页面上的定位器和操作逻辑测试用例只描述业务场景。这样页面结构变了只需要改一个地方全部用例自动适配。并行执行是另一个值得投入的方向。Playwright的RunnerNode版本内置了并行能力Python下可以借助pytest-xdist实现多进程并行会大大降低整体测试时间。但并行执行时要注意不同进程的数据隔离避免多个用例同时操作同一份测试数据导致互相干扰。监控和报告也不能落下。Playwright支持生成HTML测试报告和Trace查看器。Trace可以在用例失败后回放当时的操作录像和网络请求对排查复杂问题特别有用。我在团队里定了一条规矩所有CI失败的用例必须附带Trace文件否则不算有效报告。这一条让排查问题的效率提升了很多。几个值得继续深入的方向如果你已经把基础用法掌握了我建议往这几个方向再深挖一下。一是Playwright Test Runner的完整使用它对断言、重试、失败截图、并行等能力都做了非常好的集成适合纯前端团队。二是和持续集成体系的融合比如Jenkins或GitLab CI里docker运行Playwright需要注意容器里安装浏览器依赖的问题。三是多环境验证结合多浏览器引擎做兼容性覆盖这是Playwright相对其他工具优势最明显的地方。最后说一个我自己的体会工具再好不能解决所有测试问题。Playwright把浏览器自动化的“工程难度”大大降低了但测试策略、数据管理、团队规范这些仍然是决定自动化项目成败的关键。先把工具用熟再认真设计好自己的测试体系自动化测试才会真正成为项目的助力而不是另一个需要维护的负担。
返回列表