PlayWright 框架(二)
🍰 Playwright用于跨浏览器测试,支持Chrome、Firefox等,提供一个简单而强大的API模拟用户在浏览器中的交互操作。
1 方法汇总
- 方法汇总
- Playwright中的测试层级:Browser > Context > Page > Locator > ElementHandle。
- Page对象:直接在整个页面范围内进行元素查找和判断。
page.is_hidden(selector: str)
·····判断指定选择器对应的元素是否隐藏page.is_visible(selector: str)
····判断指定选择器对应的元素是否可见page.is_enabled(selector: str)
····判断指定选择器对应的元素是否可用page.is_editable(selector: str)
···判断指定选择器对应的元素是否可编辑page.is_disabled(selector: str)
···判断指定选择器对应的元素是否不可用page.is_checked(selector: str)
····判断指定选择器对应的单选Radio或复选Checkbox,元素是否被选中
- Locator对象:通过Page页面对象的定位方法,例如使用
page.locator(selector)
来获取ElementHandle元素句柄。locator.is_hidden()
···············判断该定位器对象对应的元素是否隐藏locator.is_visible()
··············判断该定位器对象对应的元素是否可见locator.is_enabled()
··············判断该定位器对象对应的元素是否可用locator.is_editable()
·············判断该定位器对象对应的元素是否可编辑locator.is_disabled()
·············判断该定位器对象对应的元素是否不可用locator.is_checked()
··············判断该定位器对象对应的单选Radio或复选Checkbox,元素是否被选中
- ElementHandle对象:通过使用
page.query_selector()
方法,来调用返回的ElementHandle元素句柄,一般不常用。element_handle.is_hidden()
········判断该元素句柄对象对应的元素是否隐藏element_handle.is_visible()
·······判断该元素句柄对象对应的元素是否可见element_handle.is_enabled()
·······判断该元素句柄对象对应的元素是否可用element_handle.is_editable()
······判断该元素句柄对象对应的元素是否可编辑element_handle.is_disabled()
······判断该元素句柄对象对应的元素是否不可用element_handle.is_checked()
·······判断该元素句柄对象对应的单选Radio或复选Checkbox,元素是否被选中
- Expect常用的断言方法
expect(api_response).to_be_ok()
·········断言API响应是否成功- Page页面断言
expect(page).to_have_url()
··········断言页面的URL是否符合预期expect(page).not_to_have_url()
······断言页面的URL是否不存在expect(page).to_have_title()
········断言页面的标题是否符合预期expect(page).not_to_have_title()
····断言页面的标题是否不存在
expect(locator).to_be_empty()
···········断言locator对应的元素是否为空expect(locator).to_be_hidden()
··········断言locator对应的元素是否隐藏expect(locator).to_be_visible()
·········断言locator对应的元素是否可见expect(locator).to_be_enabled()
·········断言locator对应的元素是否可用expect(locator).to_be_editable()
········断言locator对应的元素是否可编辑expect(locator).to_be_disabled()
········断言locator对应的元素是否不可用expect(locator).to_be_focused()
·········断言locator对应的元素是否处于焦点状态expect(locator).to_be_checked()
·········断言locator对应的复选框或单选框元素是否被选中expect(locator).to_have_id()
············断言locator对应的元素是否具有特定的idexpect(locator).to_have_count()
·········断言locator对应的元素数量是否符合预期expect(locator).to_have_value()
·········断言locator对应的元素的值是否符合预期expect(locator).to_have_text()
··········断言locator对应的元素是否具有特定的文本expect(locator).to_have_class()
·········断言locator对应的元素是否具有特定的类名expect(locator).to_have_css()
···········断言locator对应的元素是否具有特定的CSS样式expect(locator).to_have_values()
········断言locator对应的元素的值集合是否符合预期expect(locator).to_have_attribute()
·····断言locator对应的元素是否具有特定的属性expect(locator).to_have_js_property()
···断言locator对应的元素是否具有特定的JavaScript属性expect(locator).to_contain_text()
·······断言locator对应的元素是否包含特定的文本
1-1 页面导航*
- 页面导航
- 异步导航(Async navigation)
- 指等待页面完成导航的操作,页面完成导航后再执行后续的操作。
- 可使用
page.goto()
或page.wait_for_navigation()
方法来等待页面导航完成。 - 异步导航通常用于单一的页面导航,等待页面跳转,或加载完毕后再进行后续操作。
- 触发的导航:指代导航到新的URL、在同一网页中刷新、后退或前进,这四种情况。
- 多重导航(Multiple navigations)
- 多重导航指连续进行多次页面导航的操作,每次导航都需要等待页面完成跳转或加载。
- 可使用
page.goto()
或page.expect_navigation()
方法来明确等待特定的导航完成。 - 通常用于需进行连续多次页面跳转的情况,如应用程序中执行多步骤操作,或测试期间模拟用户浏览。
- 异步导航(Async navigation)
1 |
|
1-2 高级模式
- 高级模式
page.wait_for_function()
:用于等待指定的JavaScript函数返回true或非空结果。- 可用于检查页面上的某些条件是否满足,例如:等待特定元素出现或特定文本被更新。
1 |
|
1-3 svg元素操作
- svg元素操作
- SVG即Scalable Vector Graphics,由W3C制定,用于描述二维矢量图形的XML(可扩展标记语言)文件格式。
- 无法用普通的标签定位到,如
$x('//svg')
,只能通过name()
函数定位,如$x("//*[name()='svg']")
。 - 如果页面上有多个svg元素,
//*[name()="svg"]
将定位出全部的svg元素。- 可以通过父元素来进行区分,例如:
//*[@id="box1"]//*[name()="svg"]
。 - 可使用
and
组合其他属性,例如://*[name()="svg" and @width="500"]
。
- 可以通过父元素来进行区分,例如:
- 定位svg上的子元素,仍然可以通过
name()
函数定位,如$x("//*[name()='svg']/*[name()='path']")
。 - svg元素下的circle是可拖动的,例如:往右拖动100个像素,那么cx值就由原先的
cx="100"
变为cx=200
。
1 |
|
1-4 日期控件输入
- 日期控件输入
- 输入框是日期控件时,先看能否直接输入日期,可以直接输入的情况下就不用点开了。
- 若有
readonly
属性就不能直接输入,这种情况下可以使用JavaScript去掉属性再输入。
(1) 直接输入
1 |
|
(2) readonly
1 |
|
1-5 Trace Viewer
- Trace Viewer
- 执行自动化用例的过程中,出现一些不稳定偶然性的Bug,需复现还原Bug出现的过程。
- Playwright Trace Viewer可探索记录Playwright的测试跟踪,直观查看操作期间的情况。
- 查看
- 使用Playwright CLI在terminal中输入
playwright show-trace trace.zip
,打开跟踪。 - 通过单击每个操作,或使用时间轴悬停来查看测试的痕迹,并查看操作前后页面的状态。
- 在每个步骤中检查日志、源和网络,跟踪查看器创建一个DOM快照,可与其完全交互,打开devtools等。
- 使用Playwright CLI在terminal中输入
1 |
|
1-6 table表格标签
- table表格标签
- table表格的常用标签:table(一个表格)、tr(一行)、th(表头单元格)、td(内容单元格)。
- 使用xpath定位table表格数据,例如
/html/body/div[1]/table[1]/tbody/tr[1]/td[2]
。
(1) 表格定位
1 |
|
(2) 数据获取
1 |
|
1-7 图片相似度对比
- 图片相似度对比
- pip命令安装airtest和aircv:
pip install airtest
、pip install aircv
。 - Python安装目录中,找到Lib\site-packages\airtest\aircv\cal_confidence.py。
- 相似度对比时需用到cal_confidence.py中的函数
cal_ccoeff_confidence()
。 - 对比图时需要将图片的大小设为一致,可以使用
cv2.resize()
方法进行处理。 cv2.resize(src, dsize, dst=None, fx=None, fy=None, interpolation=None)
src
源图像、dsize
图像的大小、fx
width方向的缩放比例。fy
height方向的缩放比例、interpolation
指定插值的方式。
- pip命令安装airtest和aircv:
1 |
|
2 插件编写用例
- 插件编写用例
- pytest-playwright插件完美地继承了pytest框架和playwright基础使用的封装,满足基本工作需求。
- pip install pytest-playwright -i http://pypi.douban.com/simple/ –trusted-host pypi.douban.com
1 |
|
2-1 CLI参数
1 |
|
2-2 用例编写
- 用例编写
- 浏览器上下文使得页面在测试之间被隔离,相当于一个全新的浏览器配置文件,每个测试都会获得一个新环境。
- 可以使用各种fixture在测试之前或者之后执行代码,并在它们之间共享对象,例如:beforeEach、afterEach等。
- 内置fixture
- Function scope:这些固定装置在测试功能中请求时创建,并在测试结束时销毁。
- page:用于测试的新浏览器页面。
- context:用于测试的新浏览器上下文。
- Session scope:这些固定装置在测试函数中请求时创建,并在所有测试结束时销毁。
- browser_name:浏览器名称作为字符串。
- browser:由Playwright启动的浏览器实例。
- browser_channel:浏览器通道作为字符串。
- browser_type:当前浏览器的BrowserType实例。
- is_chromium,is_webkit,is_firefox:相应浏览器类型的布尔值。
- 自定义fixture选项:对于browser和context ,使用以下fixture来自定义启动选项。
- browser_context_args:覆盖browser.new_context()的选项,返回一个字典。
- browser_type_launch_args:覆盖browser_type.launch()的启动参数,返回一个字典。
- Function scope:这些固定装置在测试功能中请求时创建,并在测试结束时销毁。
1 |
|
(1) 跳过浏览器
1 |
|
(2) 指定浏览器
1 |
|
(3) 配置base-url
1 |
|
(4) 设置手机设备
1 |
|
(5) 忽略https错误
1 |
|
(6) 持久的context
1 |
|
(7) 浏览器窗口大小
1 |
|
2-3 多进程执行
- 多进程执行
- 原则
- 用例之间相互独立,没有依赖,每个用例都可独立运行。
- 用例执行之间没有顺序要求,随机顺序都可以正常执行。
- 每个用例都能重复执行,运行结果不会影响到其他用例。
- 使用PIP包管理工具安装pytest-xdist插件:
pip install pytest-xdist
。 - 线程(Thread)是进程(Process)内的实际执行单位,一进程可包含多个线程。
- 原则
1 |
|
2-4 命令行选项
- 命令行选项
- 测试分布算法配置:指在分布式测试执行期间使用的算法和相关设置。
- 通过测试分布,可以将测试用例并行在多个浏览器、设备或环境中执行,以加快测试执行速度。
- 常见的测试分布算法配置选项
- 轮询:将待执行的测试用例依次轮流分发给可用的执行器,以均匀地分配测试负载。
- 随机:随机选择可用的执行器来运行测试用例,从而可能在某些情况下提高测试效率。
- 基于负载:根据执行器的负载情况动态分发测试用例,以确保执行器的负载大致相等。
- 在Playwright中,dist命令行选项用于配置测试分布算法。
--repeat-each 3
:每个测试用例将被执行3次。--workers 3
:使用3个工作进程来执行测试用例。--shard 1/3
:使用3个分片中的第1个分片来执行测试用例。--max-failures 3
:如果测试用例失败的次数达到3次,则测试执行将停止。--dist loadfile
:使用基于负载的分布算法,并根据测试文件来分发测试用例。--dist loadgroup
:使用基于负载的分布算法,并根据测试分组来分发测试用例。- 使用
@pytest.mark.xdist_group(name=" ")
装饰器为测试用例指定不同的分组名称。 - 当执行测试时,pytest-xdist插件可以并行地执行同一分组的测试用例,提高执行效率。
- 使用
--dist loadscope
:使用基于负载的分布算法,并按作用域来动态分配测试用例。--dist load
(默认方式):使用基于负载的分布算法,根据执行器的负载情况来动态地分配测试用例。--dist no
:禁用测试分布,即不进行分布式测试执行,而是在单个执行器上依次执行所有测试用例。--dist worksteal
:使用worksteal算法分发用例,算法允许空闲的执行器从繁忙的执行器那里窃取用例以平衡负载。--max-sched-chunk=3
:限制并发执行的最大数量为3,从而在多线程进行测试时可以限制同时执行的测试用例数量。
- 测试分布算法配置:指在分布式测试执行期间使用的算法和相关设置。
1 |
|
2-5 插件结合使用
1 |
|
(1) pytest.ini
1 |
|
(2) 解决方案一
1 |
|
(3) 解决方案二
1 |
|
3 页面对象模型
- 页面对象模型
- POM是一种软件测试设计模式,用于在自动化测试中组织和管理网页的元素和操作。
- 该模式将每个网页视为一个独立的对象,并且使用对象来表示该页面的元素和行为。
- 基本原则是将页面结构和行为与测试代码分离开来,若结构发生变化,只需更新页面对象的代码。
- 以测试注册页面为例
- 根据输入框的内容,可以编写多个有效等价和无效等价的测试用例。
- 用例操作是在页面元素上点点点,将元素定位和操作封装成一个类。
- 搭建JForum论坛
- xampp-windows-x64-5.6.40-1-VC11-installer.exe:下载,傻瓜式安装。
- MySQL将my.ini文件中的端口3306修改为3311,Apache将httpd.conf文件中的端口80修改为81。
- /Xampp/phpMyAdmin/config.inc.php文件添加内容
$cfg['Servers'][$i]['port'] = '3311';
。 - 启动Apache、Tomcat、MySQL,下载“jforum-2.7.0.war”剪贴到Xampp/tomcat/webapps目录。
- 若浏览器正常访问“http://localhost:81/phpmyadmin/”,说明文件配置正确。
- phpMyAdmin中新建一个名为
jforum
,编码为utf8_general_ci
的数据库。 - phpMyAdmin界面,点击账户,将用户名为root的权限密码全部改为123456。
- /Xampp/phpMyAdmin/config.inc.php文件,改
['password'] = '123456';
。
- phpMyAdmin中新建一个名为
- 浏览器访问“http://127.0.0.1:8080/jforum-2.7.0/install.jsp”,部署本地论坛。
- 数据库端口3311,数据库用户账号为root,密码123456,系统管理员密码123456。
- 部署完成后访问论坛首页:http://127.0.0.1:8080/jforum-2.7.0/forums/list.page。
- 注意:访问论坛时只需启动Tomcat和MySQL,访问数据库则要开启Apache和MySQL。
- xampp-windows-x64-5.6.40-1-VC11-installer.exe:下载,傻瓜式安装。
1 |
|
3-1 封装成类
1 |
|
3-2 conftest.py
1 |
|
3-3 测试用例部分
1 |
|
4 登录相关操作
- 登录相关操作
- 身份验证方式、绕过登录验证码:以“页面对象模型”中搭建的“JForum论坛”为例进行说明。
- 登录页滑动解锁:https://www.bootstrapmb.com/item/10579。
- 滑块拼图验证码:https://www.bootstrapmb.com/item/2880。
- 登录验证码识别:https://www.bootstrapmb.com/item/8462。
- 多线程登录账号、多账号登录问题:以“页面对象模型”中搭建的“JForum论坛”为例进行说明。
4-1 身份验证方式
- 身份验证方式:以“页面对象模型”中搭建的“JForum论坛”为例进行说明。
- 保存登录cookie:先登录,将cookie保存到本地,通过加载cookie的方式解决重复登录的问题。
- Web应用程序使用基于cookie或基于令牌的身份验证,其中经过身份验证的状态存储为cookie或本地存储。
- Playwright提供方法用于从经过身份验证的上下文中检索存储状态,然后创建具有预填充状态的新上下文。
- 会话存储:会话存储很少用于存储与登录状态相关的信息,常用于特定域,并且不会跨页面加载持续存在。
- 保存登录的cookie或基于本地存储状态的身份验证,可以跨不同的浏览器使用,取决于应用程序的身份验证模型。
1 |
|
(1) 生成state.json
1 |
|
(2) 验证是否免登
1 |
|
(3) 本地会话存储
1 |
|
4-2 绕过登录验证码
- 绕过登录验证码:以“页面对象模型”中搭建的“JForum论坛”为例进行说明。
- 浏览器手工登录,再使用Playwright接管页面,绕过登录验证码操作。
- 找到Chrome属性,复制Chrome的起始位置,添加到Path环境变量下。
- 参数说明
--start-maximized
窗口最大化。--incognito
隐私模式打开、--new-window
直接打开网址。--remote-debugging-port
指定运行端口,需确保没被占用。--user-data-dir
指定运行浏览器的运行数据,新建目录,不影响系统原数据。
chrome.exe --remote-debugging-port=12345 --incognito --start-maximized --user-data-dir=
'D:\Project\Python\demo' --new-window http://127.0.0.1:8080/jforum-2.7.0/user/login.page
- CMD窗口输入命令启动浏览器,并打开JForum论坛,手动输入账号密码登录后,执行以下Playwright代码。
1 |
|
4-3 登录页滑动解锁
1 |
|
4-4 滑块拼图验证码
- 滑块拼图验证码
- 首先需要得到滑块背景图,然后计算缺口位置,最后可能还要绕过防爬虫机制,计算滑动轨迹。
- 滑块背景图可以获取到一个图片地址,缺口可能获取到图片地址,可能由Canvas标签绘制而成。
1 |
|
4-5 登录验证码识别
- 登录验证码识别
- 思路:先获取到验证码图片,定位元素,对元素截图即可,再使用ddddocr库快速识别。
- 命令安装ddddocr库:
pip install ddddocr -i https://pypi.douban.com/simple
。
1 |
|
4-6 多线程登录账号
1 |
|
5 账号切换操作
- 账号切换操作
- 以“页面对象模型”中搭建的“JForum论坛”为例进行说明。
- 用户的发帖与回帖功能,需使用Admin管理员账号,在后台管理中将验证码关闭。
- 双账号切换操作:代码实现Admin账号登录进行发帖,Elianis账号登录进行回帖。
- 这里的conftest.py文件在根目录,会被“页面对象模型”的conftest.py文件取代。
- conftest.py文件的优先级问题
- 测试文件所在目录中的conftest.py文件,具有最高的优先级。
- 测试文件所在父目录的conftest.py文件其次,依次向上查找。
- 项目根目录中的conftest.py文件优先级最低。
- 以“页面对象模型”中搭建的“JForum论坛”为例进行说明。
1 |
|
5-1 conftest.py
1 |
|
5-2 post_page.py
1 |
|
5-3 login_page.py
1 |
|
5-4 test_login.py
1 |
|
6 Mock接口返回
- Mock接口返回
- Playwright提供API,用来模拟和修改网络请求,包括HTTP和HTTPS。
- 页面执行的任何请求包括XHR和获取请求,都可以被跟踪、修改和模拟。
- 使用
page.route()
方法创建Route对象,指定要拦截的请求URL,或使用正则表达式进行匹配。 - 创建Route对象后,通过调用
route.abort()
、route.fulfill()
等来控制请求的进一步处理。
6-1 方法
- 方法
route.abort()
,中止请求,可以选择指定的错误代码。route.fetch()
,执行请求并在不满足的情况下获取结果,以修改完成响应。route.fulfill()
,用于模拟完成请求,即手动提供响应数据,并结束请求。route.fallback()
,用于指定当请求未匹配到任何拦截规则时候的回退行为。route.continue_()
,继续请求,使其按照正常的流程继续发送,并接收响应。
(1) abort()*
1 |
|
(2) fetch()
1 |
|
(3) fulfill()
1 |
|
(4) fallback()
1 |
|
(5) continue_()*
1 |
|
6-2 属性
1 |
|
7 JavaScript脚本
- JavaScript脚本
- 页面对象执行JavaScript脚本
- Playwright使用
page.evaluate()
执行JavaScript代码并返回调用执行的结果。 - 使用
page.evaluate_handle()
执行JavaScript代码,并返回执行结果的句柄。
- Playwright使用
- 定位元素执行JavaScript脚本
- 通过
locator.evaluate()
方法,对定位到的元素执行JavaScript代码。 - 使用
locator.evaluate_all()
对定位的所有元素执行JavaScript代码。
- 通过
- 页面对象执行JavaScript脚本
7-1 页面Js*
1 |
|
7-2 元素Js
1 |
|
8 Pyinstaller打包
- Pyinstaller打包
- Playwright与Pyinstaller三方插件结合使用,可用来创建独立的可执行文件。
- 例如:要打包一个files_pack.py文件。
- 先在当前文件夹中设置环境变量:
set PLAYWRIGHT_BROWSERS_PATH=0
。 - 然后命令安装文件中使用到的浏览器:
playwright install chromium
。 - 最后使用命令查看浏览器的安装路径:
playwright install --dry-run
。 - 可以看到chromium-1048和ffmpeg-1008这两个文件,后缀数字是版本号。
- 先在当前文件夹中设置环境变量:
- 打包files_pack.py文件命令如下,打包后的可执行文件可在当前目录的dist文件夹中找到。
- 依赖文件较多,首次打包需加载好一会:
pyinstaller -F files_pack.py
。 - 打包时自定义ico图标:
pyinstaller -F files_pack.py -i favicon.ico
。 - ico图标制作可以使用地址“在线制作ico图标”,提供图片线上转换即可。
- 依赖文件较多,首次打包需加载好一会:
8-1 files_pack.py
1 |
|
8-2 依赖环境配置
1 |
|
8-3 打包路径问题*
1 |
|
- 说明Playwright浏览器驱动没有被正确打包到exe文件中。
- 【方法一】手动添加浏览器驱动到打包目录:执行仍然报同个错误,不知道参数设置是否有问题?
pyinstaller --add-data="C:\Users\...\chromium-1048;playwright/driver" files_pack.py
- 【方法二】使用
--hidden-import
参数,指定需要打包的隐藏模块,将其强制打包到exe
文件中。 pyinstaller --hidden-import=playwright._impl._browser_type files_pack.py
:执行会闪退。- 【方法三】使用
--onefile
参数,将所有的文件都打包到可执行文件exe
中,就可避免路径问题了。 pyinstaller --onefile files_pack.py
:exe
文件执行还是会报错。- 【方法四】代码中使用Playwright的
install()
函数来安装浏览器驱动。 - 不知道是否环境问题,在其他电脑上打包后,可执行文件能成功被执行。
1 |
|
PlayWright 框架(二)
https://stitch-top.github.io/2023/11/02/ce-shi-kuang-jia/tf05-playwright/tf02-playwright-kuang-jia-er/