自动化神器playwright
一、前言
之前有转载过playwright的相关博客,最近也没少浏览相关文档,这次抽空做一个大致的使用总结,方便未来查看复习。
二、简介
微软开源自动化测试工具 Playwright,支持主流浏览器,包括:Chrome、Firefox、Safari 等,同时支持以无头模式、有头模式运行,并提供了同步、异步的 API,可以结合 Pytest 测试框架使用,并且支持浏览器端的自动化脚本录制等功能。
特点:
1、跨浏览器。Playwright 支持所有现代渲染引擎,包括Chromium、WebKit 和 Firefox。
2、跨平台。在 Windows、Linux 和 macOS 上进行本地或 CI、无头或有头测试。
3、跨语言。在 TypeScript、JavaScript、Python、.NET、Java 中使用Playwright API。
4、测试移动网络。适用于 Android 和 Mobile Safari 的 Google Chrome 原生移动仿真。相同的渲染引擎适用于桌面和云端。
官网地址:
https://playwright.dev
GitHub地址:
https://github.com/microsoft/playwright
三.安装
1 2 | pip install playwright # 安装playwright的python版本 playwright install # 安装playwright自带的浏览器和ffmepg,此步骤耗时较长 |
四、交互模式
Playwright 支持交互模式,即运行单行代码或者代码块,能立即给出运行结果。
由于 Playwright 支持同步和异步的 API,那么应先了解一下什么是同步和异步?
同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。
异步,执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。
下面以操作打开浏览器,访问百度首页,关闭浏览器为例。
1、同步命令
打开命令行,输入 python
进入到 Python 交互模式中,输入如下命令:
1 2 3 4 5 6 7 | from playwright.sync_api import sync_playwright playwright = sync_playwright().start() browser = playwright.chromium.launch(headless = False ) page = browser.new_page() page.goto( "https://www.baidu.com/" ) browser.close() playwright.stop() |
输入访问百度首页命令,浏览器页面会同时跳转到百度首页,同时命令行输出响应与请求的信息。
2、异步命令
打开命令行,输入 python -m asyncio
进入到 Python 交互模式中,输入如下命令:
1 2 3 4 5 6 7 | from playwright.async_api import async_playwright playwright = await async_playwright().start() browser = await playwright.chromium.launch(headless = False ) page = await browser.new_page() await page.goto( "https://www.baidu.com/" ) await browser.close() await playwright.stop() |
输入访问百度首页命令,浏览器页面也会同时跳转到百度首页,命令行也会输出响应与请求的信息。
五、录制模式
Playwright 带有命令行工具(录制功能),可用于记录用户交互并生成代码(Java、Python等)。其实就是类似于 Selenium IDE。
1、常规录制
打开命令行,输入
1 | playwright codegen |
自动打开浏览器和录制界面
通过操作(点击、输入等)浏览器页面,脚本也会自动增加操作的步骤。
此外,录制工具还可以获取元素的定位。点击停止录制,之后再点击 Explore 后,在页面点击想要定位的元素,即可获取到该元素定位的值。
作为演示,输入百度网址打开百度页面,搜索框中输入“playwright”,点击“百度一下”按钮,录制代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | from playwright.sync_api import sync_playwright def run(playwright): browser = playwright.chromium.launch(headless = False ) context = browser.new_context() # Open new page page = context.new_page() # Go to https://www.baidu.com/ page.goto( "https://www.baidu.com/" ) # Click input[name="wd"] page.click( "input[name=\"wd\"]" ) # Fill input[name="wd"] page.fill( "input[name=\"wd\"]" , "playwright" ) # Click text=百度一下 page.click( "text=百度一下" ) # assert page.url == "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=playwright&fenlei=256&oq=playwright&rsv_pq=bf1abd6c000029f7&rsv_t=1937PYyfHvfyS6fay57V1zS1iCIiYC4%2B8I6srjLqYYkXrf8H9kce%2BLQKVzA&rqlang=cn&rsv_dl=tb&rsv_enter=0&rsv_btype=t&prefixsug=playwright&rsp=5" # Click text=百度一下 # with page.expect_navigation(url="https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=playwright&fenlei=256&oq=playwright&rsv_pq=a03207ba00008498&rsv_t=ecf2ko5wPyHrjSHwUBLAZZwxkyObcfsg5ge7apN1BeAdigW%2BzzxD%2F3CJI7k&rqlang=cn&rsv_dl=tb&rsv_enter=0&rsv_btype=t&prefixsug=playwright&rsp=5"): with page.expect_navigation(): page.click( "text=百度一下" ) # Click text=百度一下 page.click( "text=百度一下" ) # assert page.url == "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=playwright&fenlei=256&oq=playwright&rsv_pq=bf1abd6c000029f7&rsv_t=1937PYyfHvfyS6fay57V1zS1iCIiYC4%2B8I6srjLqYYkXrf8H9kce%2BLQKVzA&rqlang=cn&rsv_dl=tb&rsv_enter=0&rsv_btype=t&prefixsug=playwright&rsp=5" # --------------------- context.close() browser.close() with sync_playwright() as playwright: run(playwright) |
最后将录制的脚本复制出来,可做适当的调整。
调整后的脚本代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from playwright.sync_api import sync_playwright # 导入playwright同步api def run(playwright): # 定义run方法 browser = playwright.chromium.launch(headless = False ) # 创建chromium的browser对象,当前使用的是playwright安装的自带的chromium context = browser.new_context() # 创建context对象,context之间是相互隔离的,可以理解为轻量级的浏览器实例 page = context.new_page() # 创建page对象,真正打开浏览器界面 page.goto( "https://www.baidu.com/" ) # 跳转到百度url page.fill( "input[name=\"wd\"]" , "playwright" ) # 通过css定位在搜索框中输入"playwright" with page.expect_navigation(): # 预期结果,点击"百度一下"按钮后会发生页面导航 page.click( "text=百度一下" ) # 通过playwright自定义的文字定位器定位"百度一下"按钮并点击 # --------------------- context.close() # 关闭context browser.close() # 关闭browser with sync_playwright() as playwright: # playwright使用入口,通过上下文方式 run(playwright) # 调用run方法,将playwright实例传入 |
2、模拟移动设备录制
open 可以模拟移动设备和平板设备
例如模拟 iPhone 14 访问博客园。
1 | playwright open - - device = "iPhone 14" home.cnblogs.com / u / keima / |
通过以上代码可以了解到:
- playwright支持同步和异步两种使用方法
- 不需要为每个浏览器下载webdriver
- 相比selenium多了一层context抽象
- 支持无头浏览器,且较为推荐(headless默认值为True)
- 可以使用传统定位方式(CSS,XPATH等),也有自定义的新的定位方式(如文字定位)
- 没有使用selenium的先定位元素,再进行操作的方式,而是在操作方法中传入了元素定位,定位和操作同时进行(其实也playwright也提供了单独的定位方法,作为可选)
- 很多方法使用了with的上下文语法
六、编写模式
使用 IDE(如 PyCharm、Visual Studio Code 等) 进行编写代码并运行程序。
1、启动浏览器(无头模式)
Playwright 可以启动三种浏览器中的 chromium、firefox、webkit 任何一种。
示例操作如下,打开浏览器、跳转百度、屏幕截图、输出页面标题、关闭浏览器。
1 2 3 4 5 6 7 8 9 | from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.goto( "https://www.baidu.com/" ) page.screenshot(path = "example.png" ) print (page.title()) browser.close() |
2、启动浏览器(有头模式)
默认情况下,Playwright 以无头模式运行浏览器。要查看浏览器 UI(有头模式),请在启动浏览器时传递 headless=False 标志,还可以使用 slow_mo 来减慢执行速度。
1 2 3 4 5 6 7 8 9 | from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless = False , slow_mo = 50 ) page = browser.new_page() page.goto( "https://www.baidu.com/" ) page.screenshot(path = "example.png" ) print (page.title()) browser.close() |
3、异步
Playwright 支持 API 的两种变体:同步和异步。
支持异步,如果你的项目使用 asyncio,则应该使用 async API。
1 2 3 4 5 6 7 8 9 10 11 | import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page() await page.goto( "https://www.baidu.com/" ) print (await page.title()) await browser.close() asyncio.run(main()) |
4、context
Playwright 支持上下文管理:一个浏览器实例下可以有多个context,将浏览器分割成不同的上下文,以实现会话的分离。
如需要不同用户登录同一个网页,不需要创建多个浏览器实例,只需要创建多个context即可。
1 2 | context1 = browser.new_context() context2 = browser.new_context() |
5、page
一个context下可以有多个page,一个page就代表一个浏览器的标签页或弹出窗口,用于进行页面操作。
1 2 3 4 5 6 7 8 9 10 | page = context.new_page() # 显式导航,类似于在浏览器中输入URL page.goto( 'https://www.baidu.com/' ) # 在输入框中输入字符 page.fill( '#search' , '百度' ) # 点击提交按钮 page.click( '#submit' ) # 打印当前url print (page.url) |
6、browser
Playwright内置了很多支持的浏览器驱动,不用再像selenium那样去寻找对应匹配版本的浏览器驱动版本下载。
只需要创建相应的browser实例。
1 2 | browser = playwright.chromium.launch() #Chrome browser = playwright.firefox.launch() #火狐 |
7、frame
一个页面至少包含一个主frame,新的frame通过iframe标签定义,frame之间可以进行嵌套,只有先定位到frame才能对frame里面的元素进行定位和操作。playwright默认使用page进行的元素操作会重定向到主frame上。
1 2 3 4 5 6 7 8 9 10 11 12 | # 通过名称获得frame frame = page.frame( 'frame-login' ) # 通过frame的url获得frame frame = page.frame(url = r '.*domain.*' ) # 通过选择器获得frame frame_element_handle = page.query_selector( '.frame-class' ) frame = frame_element_handle.content_frame() # 操作frame中的元素 frame.fill( '#username-input' , 'JoJo' ) |
8、选择器
所有元素操作都需要使用选择器定位到要操作的元素,playwright同时支持css、xpath和自定义的选择器,使用时无需指定类型,playwright会自动进行判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # Using data-test-id= selector engine page.click( 'data-test-id=foo' ) # CSS and XPath selector engines are automatically detected page.click( 'div' ) page.click( '//html/body/div' ) # Find node by text substring page.click( 'text=Hello w' ) # Explicit CSS and XPath notation page.click( 'css=div' ) page.click( 'xpath=//html/body/div' ) # Click an element with text 'Sign Up' inside of a #free-month-promo. page.click( '#free-month-promo >> text=Sign Up' ) # Capture textContent of a section that contains an element with text 'Selectors'. section_text = page.eval_on_selector( '*css=section >> text=Selectors' , 'e => e.textContent' ) |
9、自动等待
像page.click(selector)、page.fill(selector, value)之类的元素操作会自动等待元素可见且可操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # Playwright 会等待 #search 元素出现在 DOM 中 page.fill( '#search' , 'query' ) # Playwright 会等待元素停止动画并接受点击 page.click( '#search' ) # 等待 #search 出现在 DOM 中 page.wait_for_selector( '#search' , state = 'attached' ) # 等待 #promo 可见, 例如具有 `visibility:visible` page.wait_for_selector( '#promo' ) # 等待 #details 变得不可见, 例如通过 `display:none`. page.wait_for_selector( '#details' , state = 'hidden' ) # 等待 #promo 从 DOM 中移除 page.wait_for_selector( '#promo' , state = 'detached' ) |
七、实例
1、网易云签到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | import time from playwright.sync_api import Playwright, sync_playwright, expect def run(playwright: Playwright) - > None : browser = playwright.chromium.launch(headless = False ) context = browser.new_context(viewport = { 'width' : 1920 , 'height' : 1080 }) # Open new page page = context.new_page() # Go to https://music.163.com/ page.goto( "https://music.163.com/" ) # Click text=用户登录 page.frame_locator( "iframe[name=\"contentFrame\"]" ).locator( "text=用户登录" ).click() # Click text=选择其他登录模式 page.locator( "text=选择其他登录模式" ).click() # Check input[type="checkbox"] page.locator( "input[type=\"checkbox\"]" ).check() # Click text=QQ登录 with page.expect_popup() as popup_info: page.locator( "text=QQ登录" ).click() page1 = popup_info.value # Click #img_out_**** page1.frame_locator( "iframe[name=\"ptlogin_iframe\"]" ).locator( "#img_out_****" ).click() # token用*代替 page1.wait_for_url(f "https://music.163.com/back/sns?key=*" ) # Close page page1.close() # Click text=我的音乐 page.locator( "text=我的音乐" ).click() page.wait_for_url( "https://music.163.com/#/my/m/music/playlist?id=152392364" ) time.sleep( 3 ) # Click a:has-text("播放") page.frame_locator( "iframe[name=\"contentFrame\"]" ).locator( "a:has-text(\"播放\")" ).click() time.sleep( 10 ) # Click text=网易云音乐 page.locator( "text=网易云音乐" ).click() page.wait_for_url( "https://music.163.com/#" ) # Click a:has-text("签到") page.frame_locator( "iframe[name=\"contentFrame\"]" ).locator( '//*[@id="discover-module"]/div[2]/div[1]/div/div/div/div/a/i' ).click() time.sleep( 10 ) # Close page page.close() # --------------------- context.close() browser.close() print ( '******************************* 签到完成 *******************************' ) with sync_playwright() as playwright: run(playwright) |
2、B站签到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | from playwright.sync_api import Playwright, sync_playwright, expect def run(playwright: Playwright) - > None : browser = playwright.chromium.launch(headless = False ) context = browser.new_context(viewport = { 'width' : 1920 , 'height' : 1080 }) # Open new page page = context.new_page() # Go to https://www.bilibili.com/ page.goto( "https://www.bilibili.com/" ) # Click span:has-text("登录") page.locator( "span:has-text(\"登录\")" ).click() # Click text=QQ登录 with page.expect_popup() as popup_info: page.locator( "text=QQ登录" ).click() page1 = popup_info.value # Click text=**** page1.frame_locator( "iframe[name=\"ptlogin_iframe\"]" ).locator( "text=1170527913 懿曲折扇情" ).click() page1.wait_for_url( "https://www.bilibili.com/" ) # Click text=创作中心 with page1.expect_popup() as popup_info: page1.locator( "text=创作中心" ).click() page2 = popup_info.value # Click #canvas-wrap img >> nth=2 page2.locator( "#canvas-wrap img" ).nth( 2 ).click() # Click #canvas-wrap img >> nth=2 page2.locator( "#canvas-wrap img" ).nth( 2 ).click() # Click #canvas-wrap img >> nth=2 page2.locator( "#canvas-wrap img" ).nth( 2 ).click() # Click #canvas-wrap img >> nth=1 page2.locator( "#canvas-wrap img" ).nth( 1 ).click() page2.hover( '//*[@id="app"]/div[1]/div/div[2]/span[1]/a/img' ) # Click text=直播中心 with page2.expect_popup() as popup_info: page2.locator( "text=直播中心" ).click() page3 = popup_info.value # Click text=签到 >> nth=0 page3.locator( "text=签到" ).first.click() # Click span:has-text("22") >> nth=0 page3.locator( '//div[@class="checkin-btn t-center pointer"]' ).first.click() # Close page page3.close() # Close page page2.close() # Close page page1.close() # Close page page.close() # --------------------- context.close() browser.close() with sync_playwright() as playwright: run(playwright) |
八、自动等待Options
等待API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | element_handle.is_checked() element_handle.is_disabled() element_handle.is_editable() element_handle.is_enabled() element_handle.is_hidden() element_handle.is_visible() page.is_checked(selector, * * kwargs) page.is_disabled(selector, * * kwargs) page.is_editable(selector, * * kwargs) page.is_enabled(selector, * * kwargs) page.is_hidden(selector, * * kwargs) page.is_visible(selector, * * kwargs) locator.is_checked( * * kwargs) locator.is_disabled( * * kwargs) locator.is_editable( * * kwargs) locator.is_enabled( * * kwargs) locator.is_hidden( * * kwargs) locator.is_visible( * * kwargs) |
九、断言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | expect(locator).not_to_be_checked( * * kwargs) expect(locator).not_to_be_disabled( * * kwargs) expect(locator).not_to_be_editable( * * kwargs) expect(locator).not_to_be_empty( * * kwargs) expect(locator).not_to_be_enabled( * * kwargs) expect(locator).not_to_be_focused( * * kwargs) expect(locator).not_to_be_hidden( * * kwargs) expect(locator).not_to_be_visible( * * kwargs) expect(locator).not_to_contain_text(expected, * * kwargs) expect(locator).not_to_have_attribute(name, value, * * kwargs) expect(locator).not_to_have_class(expected, * * kwargs) expect(locator).not_to_have_count(count, * * kwargs) expect(locator).not_to_have_css(name, value, * * kwargs) expect(locator).not_to_have_id( id , * * kwargs) expect(locator).not_to_have_js_property(name, value, * * kwargs) expect(locator).not_to_have_text(expected, * * kwargs) expect(locator).not_to_have_value(value, * * kwargs) expect(locator).not_to_have_values(values, * * kwargs) expect(locator).to_be_checked( * * kwargs) expect(locator).to_be_disabled( * * kwargs) expect(locator).to_be_editable( * * kwargs) expect(locator).to_be_empty( * * kwargs) expect(locator).to_be_enabled( * * kwargs) expect(locator).to_be_focused( * * kwargs) expect(locator).to_be_hidden( * * kwargs) expect(locator).to_be_visible( * * kwargs) expect(locator).to_contain_text(expected, * * kwargs) expect(locator).to_have_attribute(name, value, * * kwargs) expect(locator).to_have_class(expected, * * kwargs) expect(locator).to_have_count(count, * * kwargs) expect(locator).to_have_css(name, value, * * kwargs) expect(locator).to_have_id( id , * * kwargs) expect(locator).to_have_js_property(name, value, * * kwargs) expect(locator).to_have_text(expected, * * kwargs) expect(locator).to_have_value(value, * * kwargs) expect(locator).to_have_values(values, * * kwargs) expect(page).not_to_have_title(title_or_reg_exp, * * kwargs) expect(page).not_to_have_url(url_or_reg_exp, * * kwargs) expect(page).to_have_title(title_or_reg_exp, * * kwargs) expect(page).to_have_url(url_or_reg_exp, * * kwargs) expect(api_response).not_to_be_ok() expect(api_response).to_be_ok() |
使用
1 2 | page.locator( "#submit-button" ).click() expect(page.locator( ".status" )).to_have_text( "Submitted" ) |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)