最近团队少了好些人,有点忙不过来,虽然是有覆盖好多API测试,但是端到端的测试,还是少不了UI。
发现微软有个好工具,Playwright, 关键是可以录制,比起传统的selenium, 速度快很多,不需要那么繁杂的定位元素,等待等各种操作。关键是,不需要那么多版本的driver.
如果实在忙不过来的时候,还可以录制一下,作为回归的测试。
跨浏览器和平台
跨浏览器。Playwright 支持所有现代渲染引擎,包括 Chromium、WebKit 和 Firefox。
跨平台。在 Windows、Linux 和 macOS 上进行本地测试或在 CI 上进行无头或有头测试。
跨语言。在TypeScript、JavaScript、Python、.NET、Java中使用 Playwright API 。
测试移动网络。适用于 Android 和 Mobile Safari 的 Google Chrome 浏览器的本机移动仿真。相同的渲染引擎适用于您的桌面和云端。
稳定性
自动等待。Playwright 在执行动作之前等待元素可操作。它还具有一组丰富的内省事件。两者的结合消除了人为超时的需要——这是不稳定测试的主要原因。
Web优先断言。Playwright 断言是专门为动态网络创建的。检查会自动重试,直到满足必要的条件。
追踪。配置测试重试策略,捕获执行跟踪、视频、屏幕截图。
运行机制
浏览器在不同进程中运行属于不同来源的 Web 内容。Playwright 与现代浏览器架构保持一致,并在进程外运行测试。这使得 Playwright 摆脱了典型的进程内测试运行器的限制。
测试跨越多个选项卡、多个来源和多个用户的场景。为不同的用户创建具有不同上下文的场景,并在您的服务器上运行它们,所有这些都在一次测试中完成。
可信事件。悬停元素,与动态控件交互,产生可信事件。Playwright 使用与真实用户无法区分的真实浏览器输入管道。
测试框架,穿透 Shadow DOM。Playwright 选择器穿透影子 DOM 并允许无缝地输入帧。
完全隔离-快速执行
浏览器上下文。Playwright 为每个测试创建一个浏览器上下文。浏览器上下文相当于一个全新的浏览器配置文件。这提供了零开销的完全测试隔离。创建一个新的浏览器上下文只需要几毫秒。
登录一次。保存上下文的身份验证状态并在所有测试中重用它。这绕过了每个测试中的重复登录操作,但提供了独立测试的完全隔离。
强大的工具
代码生成器。通过记录您的操作来生成测试。将它们保存为任何语言。
调试。检查页面、生成选择器、逐步执行测试、查看点击点、探索执行日志。
跟踪查看器。捕获所有信息以调查测试失败。Playwright 跟踪包含测试执行截屏、实时 DOM 快照、动作资源管理器、测试源等等。
安装 playwright:
代码语言:javascript复制pip install playwright
安装所需的浏览器 chromium,firefox 和 webkit:
代码语言:javascript复制playwright install
仅需这一步即可安装所需的浏览器,并且不需要安装驱动包了(解决了selenium启动浏览器,总是要找对应驱动包的痛点)
三,简单使用
Playwright 支持2种运行方式:同步和异步。以下为同步:
代码语言:javascript复制from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False) # 启动 chromium 浏览器
page = browser.new_page() # 打开一个标签页
page.goto("https://www.baidu.com") # 打开百度地址
print(page.title()) # 打印当前页面title
browser.close() # 关闭浏览器对象
如果不习惯with语句,也可以用start() 和stop() 的方式:
代码语言:javascript复制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()
四:定位元素
playwright 可以通过 CSS selector, XPath selector, HTML 属性(比如 id, data-test-id)或者是 text 文本内容定位元素。
Selector 选择器
操作元素,可以先定位再操作
代码语言:javascript复制 # 先定位再操作
page.locator('#kw').fill("playwright")
page.locator('#su').click()
也可以直接调用fill 和 click 方法,传Selector选择器
代码语言:javascript复制page.fill('#kw', "playwright")
page.click('#su')
CSS 或 XPath 选择器
可以使用xpath 和 css 元素
代码语言:javascript复制# CSS and XPath
page.fill('css=#kw', "playwright")
page.click('xpath=//*[@id="su"]')
当 DOM 结构发生变化时,这些选择器可能会中断。长 CSS 或 XPath 链是导致测试不稳定。
text 文本选择器
文本选择器是一个非常实用的定位方式,根据页面上看到的text文本就可以定位了,比如我们经常在selenium中使用xpath 的文本选择器定位
完全匹配文本 //*[text()="百度一下"]
包含某个文本 //*[contains(text(),"百度一下")]
playwright 封装了text文本定位的方式,也可以支持2种文本定位方式
代码语言:javascript复制page.click("text=百度一下") # 模糊匹配
page.click("text='百度一下 '") # 完全匹配
关键这玩意,还可以拦截API请求,这个在测UI的过程中,就可以获取API的数据信息,就可以作为爬虫使用了。
截图
截取当前屏幕
如果仅仅截取当前屏幕(浏览器)上能看到的部分,那么可以使用如下语法:
代码语言:javascript复制page.screenshot(path="截图保存路径")
截取整个页面
有时候,页面可能会比较长,一个屏幕无法全部展示出来。如果想截取整个页面,怎么办呢?
page.screenshot提供了full_page参数,语法如下:
代码语言:javascript复制page.screenshot(path="截图保存路径",full_page=True)
截取页面的一部分
有时候,我们可能只想截取页面的一部分,那么,Playwright也支持将想要截取的部分筛选出来,然后调用截图API进行截图。
示例:打开主页,截取页面部分,保存为一张图片。
代码语言:javascript复制from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto('https:')
page.locator('.main-footer').screenshot(path=f'screenshot3.png')
page.wait_for_timeout(2000)
browser.close()
简单录制了一个脚本:
代码语言:javascript复制from playwright.sync_api import Playwright, sync_playwright, expect
import re
import json
def handle(response):
if response is not None:
if not response.url.endswith(("png","jpg","css","js","ico","woff","woff2","ttf","svg","eot")):
print(response.request.url)
if response.request.method == "POST":
print(response.request.post_data)
else:
print(response.request.method)
print(response.request.headers)
if response.request.headers.get("Content-Type") == "application/json":
print(response.request.post_data_json)
if "authorization" in response.request.headers:
print(response.request.headers.get("authorization"))
if isinstance(response.json(),dict):
print(response.json())
if isinstance(response.text(),list):
print(response.text())
if isinstance(response.text(),str):
print(response.text())
if isinstance(response.text(),int):
print(response.text())
if isinstance(response.text(),float):
print(response.text())
if isinstance(response.text(),bool):
print(response.text())
def run(playwright: Playwright) -> None:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
# page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
# page.route(re.compile(r"(.png$)|(.jpg$)"), lambda route: route.abort())
# # 监听请求事件
# page.on("request", lambda request: print("request: ", request.method, request.url))
# # 监听响应事件
# page.on("response", lambda response: print("response: ", response.status, response.url,"n",response.body,"n",response. headers))
page.on("response", lambda response:handle(response=response))
page.goto("https://10.4.131.110/console/")
page.frame_locator("iframe").get_by_placeholder("请输入账号").click()
page.frame_locator("iframe").get_by_placeholder("请输入账号").fill("admin")
page.frame_locator("iframe").get_by_placeholder("请输入账号").press("Enter")
page.frame_locator("iframe").get_by_placeholder("请输入密码").click()
page.frame_locator("iframe").get_by_placeholder("请输入密码").fill("eisoo.com123")
page.frame_locator("iframe").get_by_placeholder("请输入密码").press("Enter")
page.get_by_role("menuitem", name=" 安全管理").click()
# 验证:
# blog:https://www.cnblogs.com/yoyoketang/
#from playwright.sync_api import expect
# locator = page.get_by_label("Subscribe to newsletter")
# expect(locator).to_be_checked()
# locator = page.locator('.my-element')
#expect(locator).to_be_visible()
# 截图 单个元素
# page.locator(".header").screenshot(path="screenshot.png")
# 截取长图
#page.screenshot(path="screenshot.png", full_page=True)
# 截取整个页面
# page.screenshot(path="screenshot.png")
page.get_by_text("文档安全策略配置").click()
page.get_by_text("权限申请策略").click()
page.get_by_role("button", name=" 新建策略").click()
page.get_by_label("部分文档库").check()
page.get_by_role("button", name="选择").click()
page.locator("div").filter(has_text=re.compile(r"^1$")).nth(1).click()
page.get_by_role("button", name="确定").click()
page.get_by_label("部分用户").check()
page.locator("form").filter(has_text="适用范围:该条策略将应用到所选用户上全部用户部分用户选择").get_by_role("button", name="选择").click()
page.get_by_role("link", name=" a").click()
page.get_by_role("button", name="").click()
page.locator("form").filter(has_text="适用范围:该条策略将应用到所选用户上全部用户部分用户选择").get_by_role("button", name="选择").click()
page.get_by_role("link", name=" 组织结构").click()
page.locator("div").filter(has_text=re.compile(r"^组织结构$")).nth(1).click()
page.locator("body").press("Enter")
page.get_by_role("button", name="").click()
page.locator("form").filter(has_text="适用范围:该条策略将应用到所选用户上全部用户部分用户选择").get_by_role("button", name="选择").click()
page.get_by_text("a").click()
page.get_by_role("button", name="确定").click()
page.locator("div").filter(has_text=re.compile(r"^管控文档库文档库:该条策略将应用到所选文档库上全部文档库部分文档库1选择适用范围适用范围:该条策略将应用到所选用户上全部用户部分用户a选择$")).first.click()
page.get_by_role("button", name=" 执行规则").click()
page.get_by_role("button", name="close").click()
page.get_by_role("button", name="确定").click()
page.locator("div").filter(has_text=re.compile(r"^审核$")).nth(1).click()
page.get_by_role("button", name="设置流程").click()
page.get_by_role("dialog").filter(has_text="审核环节点击审核环节,可设置此环节的审核员及审核模式。1 / 3上一步下一步").get_by_role("button", name="下一步").click()
page.get_by_role("dialog").filter(has_text="添加环节点击添加审核环节,可设置不同审核模式下的多层级审批。2 / 3上一步下一步").get_by_role("button", name="下一步").click()
page.get_by_role("button", name="我知道了").click()
page.locator("div").filter(has_text=re.compile(r"^设置审核模式和审核员$")).nth(2).click()
page.get_by_role("radio", name="指定用户审核").click()
page.get_by_role("treeitem", name=" a").locator("i").click()
page.get_by_text("cc", exact=True).click()
page.get_by_role("button", name="close drawer").click()
page.get_by_text("从已有审核流程模板中选择>> 发起审核您还未对此环节进行设置。审核设置审核模式和审核员您还未对此环节进行设置。流程结束").click()
page.get_by_role("button", name="close drawer").click()
page.close()
# ---------------------
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)
看起来虽然好像很乱,但是运行起来还不错。
发现很多前端都是框架写的,定位元素不容易,就不需要去做验证了,用接口来验证也就可以了,这样也就达到了验证效果。
如果接口录制比较顺利,还可以做流量回放来测试。