快速进行UI自动化

2024-01-15 12:04:37 浏览数 (2)

最近团队少了好些人,有点忙不过来,虽然是有覆盖好多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)

看起来虽然好像很乱,但是运行起来还不错。

发现很多前端都是框架写的,定位元素不容易,就不需要去做验证了,用接口来验证也就可以了,这样也就达到了验证效果。

如果接口录制比较顺利,还可以做流量回放来测试。

0 人点赞