Playwright Python 自动化测试:零基础半个月的学习与踩坑记录

背景:前端开发,公司要求项目写自动化测试,没测试经验,过了两遍Python语法,开始了自动化测试的学习与使用📒

你是否正在寻找一种从零开始的方法来为你的项目编写 UI 自动化测试?如果是这样,你来对地方了

本文将利用我的学习和实际经验,向你介绍 Playwright 和 Python 在 UI 自动化测试中的应用

这不仅是一个简单的教程,更是一个结合了真实业务逻辑片段的实用入门指南

引言

在较长的产品周期中,自动化测试是确保应用质量和性能的关键步骤。这里记录了一个前端开发者从零开始,学习Python和Playwright来构建自动化测试的过程。Playwright 是一个强大的自动化测试工具,支持多种浏览器,本文将从基础安装到脚本编写的每一个步骤进行详细介绍,重点是示例。

建议:等你遇到问题再来查,或许会帮助你解决

测试大佬勿喷,欢迎指正

playWright 自带的元素获取工具

效果展示:

20240423-175716.gif

工具源文档

playwright codegen 是 Playwright 测试框架的一个功能,它可以自动记录你在浏览器中的操作并生成相应的测试代码。这是一个非常有用的工具,尤其是对于快速开始编写测试或者为现有的用户操作创建自动化测试脚本。

使用 playwright codegen 的步骤如下:

安装 Playwright:如果你还没有安装 Playwright,可以通过下面命令安装

pip install playwright

运行 Codegen:在命令行中输入 playwright codegen 后跟你想要测试的网站的 URL。例如:

playwright codegen https://www.baidu.com

如果不提供 URL,codegen 会打开一个空白的浏览器窗口,您可以在其中输入您想要测试的网站的 URL。

记录操作: 在打开的浏览器窗口中执行你想要测试的操作。Playwright 会监视您的操作并生成相应的测试代码。

生成断言: 你可以通过点击 Playwright Inspector 窗口中的图标来生成断言,然后点击页面上的元素来对其进行断言。

停止记录: 完成操作后,点击“停止记录”按钮。然后,你可以使用“复制”按钮将生成的代码复制到您的编辑器中。

编辑和保存测试: 你可以在编辑器中进一步编辑和完善生成的测试代码,然后将其保存为测试文件。 playwright codegen 是快速生成测试代码的强大工具,它可以节省时间并确保测试准确录制了用户的实际操作。

如下:

20240423-175716.gif

元素获取示例

语法page.locator()

page.locator(".menu-delete-modal") 是 Playwright 测试库中的一个语法,用于定位页面上的元素,并且可以链式调用多种操作。这里,.menu-delete-modal 是一个CSS类,它会选择页面上所有具有 menu-delete-modal 类的元素。page.locator 方法返回一个 Locator 对象,代表了页面上的一个或多个DOM元素,可以对这些元素执行各种操作,比如点击、填写表单等。

以下是一些 Python 示例,展示了获取元素的常用方法page.locator()

  1. 定位元素:使用 CSS 选择器或 XPath 来定位页面上的元素。

page.locator('.example').click()  # 点击类名为example的元素
  1. 文本内容定位:通过元素的文本内容来定位。

    page.locator('text="登录"').click()  # 点击文本为“登录”的元素
  2. 属性定位:通过元素的属性来定位。

    page.locator('[name="email"]').fill('example@example.com')  # 填写name属性为email的输入框
  3. 等待元素:等待元素出现在页面上。

    page.locator('.loading').wait_for(state="hidden")  # 等待加载元素消失,默认等待元素出现
  4. 获取元素属性:获取元素的属性值。

    href = page.locator('.link').get_attribute('href')  # 获取链接的href属性
  5. 处理多个元素:对页面上的一组元素进行操作。

    items = page.locator('.list-item').element_handles() for item in items:    item.click()  # 点击每个列表项
  6. 元素的父级元素: 拿到父级,获取父级下其他数据是。

    items = page.locator('.list-item').element_handles()
    for item in items:
        item.click()  # 点击每个列表项
    

示例-> 获取类名 menu-delete-modal 弹框下一个按钮

parent_element = page.locator('text="登录"').locator('xpath=..')

示例-> 使用locator选择器来点击一个确定按钮

page.locator(".menu-delete-modal").click()

注意:在严格模式,返回的是一个或多个元素,存在差异。如果是一个元素,可以直接执行Locator对象上的元素操作方法,一些静态方法,如:click(), inner_html()...;多个元素的情况不能执行click(),inner_html()...;通过count() 可以查看有多少元素。如图

一个元素:

image-20240409103518052.png

多个元素:

image-20240409103622830.png

想要点击其中一个元素可以使用.nth(index),其中 index 是从 0 开始的索引:

page.locator(".el-button").nth(0).click()  # 点击第一个按钮
page.locator(".el-button").nth(1).click()  # 点击第二个按钮
# 以此类推...

如果需要对所有匹配的元素执行操作,可以遍历它们:

# 方式1
buttons = page.locator(".el-button")
count = buttons.count()
for i in range(count):
    buttons.nth(i).click()

# 方式2
button_handles = page.locator(".el-button").element_handles()
# 遍历并点击每个按钮
for button_handle in button_handles:
    button_handle.click()
  1. page.get_by_role()

get_by_role 是 Playwright 测试库中的一个功能,它允许你根据元素的 ARIA 角色 来定位页面上的元素。当您使用 get_by_role 时,通常也会传递一个可访问名称,以便精确地定位到特定元素。

例如,如果你想定位并且点击页面上名为“登录”的按钮,可以使用以下代码:

page.locator(".menu-delete-modal").get_by_role("button", name="确定").click()

这里按钮在浏览器的元素界面是button下包裹 span,确定文字在span内,如下:

image-20240409102104163.png

name的值采用的是模糊匹配,name为:确,也可以正常执行

对前端开发者来说,直接使用 HTML 元素和类名选择器更直观和方便。ARIA 角色主要是为了提高网页的无障碍性,帮助使用辅助技术的用户更好地理解和导航网页内容。

示例-> get_by_text 精准获取一个元素

界面:

image-20240411173707556.png

代码如下:

page.locator('.menu-config-dropdown-menu').get_by_text('员工')

执行发现会获取两个元素

{Error}strict mode violation: locator(".menu-config-dropdown-menu").get_by_text("员工") resolved to 2 elements:

  1. aka get_by_text("员工", exact=True)
  2. aka get_by_text("全部员工")

想要精准匹配,如下哦

page.locator('.menu-config-dropdown-menu').get_by_text('员工', exact=True)

示例-> 获取元素的父级元素

期望的效果是通过标题名称获取到包装元素,最终调用如下:

menu_form_design_page.get_component_by_comp_name('单行输入333').click()

场景如下:

image-20240419145205549.png

函数如下:

def get_component_by_comp_name(self, comp_name: str) -> ComponentWrapper:
    """
    # 通过组件名获取组件,返回一个组件
    """
    component = self.form_item_list.get_by_text(f'{comp_name}', exact=True).first  # 通过名称匹配到标题
    if component.count() != 1:
        raise Exception(f'未找到组件{comp_name}')
    while component.get_attribute('data-ui-test-component-type') is None: # 循环向上找目标父元素
        component = component.locator('xpath=..')
    return ComponentWrapper(self.page, component)  # 自定的一些目标元素包装类,用于封装一些常用属性和方法

ComponentWrapper类如下:

from playwright.async_api import Locator, Page

class ComponentWrapper:
    def __init__(self, page: Page, component_locator: Locator):
        self.page = component_locator.page
        self.comp_locator = component_locator
        self.comp_type = component_locator.get_attribute("data-ui-test-component-type")
        self.comp_name = component_locator.get_attribute("data-ui-test-component-label")
        self.copy_btn = component_locator.locator(".svg-x-lib-copy-add").first
        self.delete_btn = component_locator.locator(".svg-delete-icon").first
        self.delete_dialog = page.locator(".delete-component-modal").first
        self.cancel_btn = self.delete_dialog.get_by_role("button", name="取消")
        self.confirm_btn = self.delete_dialog.get_by_role("button", name="确认")
        self.comp_locator.click = self.component_wrapper_click  # 重写click方法

    # 删除组件
    def delete_comp(self):
        self.comp_locator.click()
        self.delete_btn.click()
        if self.delete_dialog.is_visible():
            self.confirm_btn.click()

    # 点击方法
    def component_wrapper_click(self):
        self.comp_locator.evaluate("element => {element.click()}")  # !!可以执行JavaScript代码,前端觉得很合理🦫

元素操作

示例-> 拖拽元素

# 向指定子表组件中添加指定类型组件,传入指定子表名称和组件类型列表
def add_component_to_specified_son_table(self, son_table_name: str, component_enum_list: list[OriginComp], callback_fn: callable = None):
    # 根据名称获目标表格组件
    son_table = self.get_component_by_comp_name(son_table_name)
    # 悬停在目标配置区域,滚动鼠标到子表组件的位置,之前发现的一个问题,如果子表格组件不在可视区域内,会导致无法拖拽某些组件会有问题
    self.form_item_list.hover()
    self.page.mouse.wheel(
    delta_y=son_table.comp_locator.evaluate("(el) => el.getBoundingClientRect()")['y'],
    delta_x=0
    )
    # 断言子表格组件的数量为1,如果不为1,说明定位到的不是唯一的子表格组件
    assert son_table.comp_locator.count() == 1
    # 获取原始组件列表,被拖拽内容
    component_type_list = Components.comp_enum_list_to_type_list(component_enum_list)
    component_list = self.get_origin_components(component_type_list)
    for component in component_list:
        component.hover()
        self.page.wait_for_timeout(500)
        self.page.mouse.down()
        # 获取子表格组件的矩形区域
        form_panel_form_widget_son_table_rect = son_table.comp_locator.locator(
        '> .form-widget-son-table .grid-draggable > .drag-area'
        ).evaluate("(el) => el.getBoundingClientRect()")
        # 计算矩形区域的x和y坐标,相对于页面左上角
        form_panel_form_widget_son_table_rect_x = form_panel_form_widget_son_table_rect['x'] + 50
        form_panel_form_widget_son_table_rect_y = form_panel_form_widget_son_table_rect['y'] + 80

        # 移动鼠标到计算出的y坐标位置,分两步也是因为有些组件从左上角开始拖拽会有问题,所以先向下,再向右
        self.page.mouse.move(
            x=0,
            y=form_panel_form_widget_son_table_rect_y,
            steps=10
        )
        self.page.mouse.move(
            x=form_panel_form_widget_son_table_rect_x,
            y=form_panel_form_widget_son_table_rect_y,
            steps=10
        )
        self.page.wait_for_timeout(500)
        self.page.mouse.up()
        # 完成一次拖拽操作,之后执行回调函数。我在Python里面写JavaScript代码,前端觉得很合理🦫
        if callback_fn:
            callback_fn()

示例-> Python playWright 执行浏览器JavaScript脚本代码

  1. 点击元素
def click_element(self, element: Locator):
    """
    # 点击元素
    """
    element.evaluate("element => {element.click()}")
  1. 有的时候playWright的click()方法会有问题,点击不到目标元素,可以使用JavaScript代码来执行点击操作

下面是一个组件的封装类,里面覆盖了点击元素的方法,使用了evaluate方法执行JavaScript代码:

from playwright.async_api import Locator, Page
class ElementManager:
    def __init__(self, page: Page, element_locator: Locator):
        self.page = element_locator.page
        self.elem_locator = element_locator
        self.elem_type = element_locator.get_attribute("data-test-elem-type")
        self.elem_label = element_locator.get_attribute("data-test-elem-name")
        self.copy_element = element_locator.locator(".icon-copy-element").first
        self.remove_element = element_locator.locator(".icon-remove").first
        # 重写点击事件
        self.elem_locator.click = self.element_click_action

    # 元素点击动作
    def element_click_action(self):
        self.elem_locator.evaluate("element => {element.click()}")

最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

在这里插入图片描述

 ​​​​软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值