一.playwright
1.学习优势
Playwright是-个跨浏览器的自动化测试工具,它支 持Chromium、firefox 和WebKit。Playwright 可以用于UI自动化测试,具有以下特点:
跨浏览器和平台
- 跨浏览器。Playwright 支持所有现代渲染引擎,包括 Chromium、WebKit (Safari 的浏览器引擎)和 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 快照、动作资源管理器、测试源等。
2.playwright环境准备
安装 playwright:
pip install playwright
安装所需的浏览器 chromium,firefox 和 webkit:
playwright install
仅需这一步即可安装所需的浏览器,并且不需要安装驱动包了(解决了selenium启动浏览器, 总是要找对应驱动包的痛点)
二.playwright入门
1.录制生成脚本
Playwright 具有开箱即用的生成测试的能力,是快速开始测试的好方法。它将打开两个窗口,一 个是浏览器窗口,您可以在其中与要测试的网站进行交互,另一个是 Playwright Inspector 窗 口,您可以在其中记录测试、复制测试、清除测试以及更改测试语言。
使用命令行启动
playwright codegen http://网站地址
登录框输入账号和密码点登录为例
一个完整的登录流程代码生成如下
from playwright.sync_api import Playwright, sync_playwright, expect
def run(playwright: Playwright)-> None:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("http://127.0.0.1:8000/login.html")
page.get_by_placeholder("请输入用户名").click()
page.get_by_placeholder("请输入用户名").fill("yoyo")
page.get_by_placeholder("请输入密码").click()
page.get_by_placeholder("请输入密码").fill("aa123456")
page.get_by_role("button", name="立即登录 >").click()
# --------------------
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)
还可以选择生成异步代码
from playwright.sync_api import Page, expect
def test_example(page: Page)-> None:
page.goto("http://127.0.0.1:8000/login.html")
page.get_by_placeholder("请输入用户名").click()
page.get_by_placeholder("请输入用户名").fill("yoyo")
page.get_by_placeholder("请输入密码").click()
page.get_by_placeholder("请输入密码").fill("aa123456")
page.get_by_role("button", name="立即登录 >").click()
2.pause断点和 inspector 使用
在运行selenium脚本的时候,我们通常习惯用sleep去让页面暂停,打开console 输入 $(selector) 或者 $x(xpath) 去调试定位页面的元素。 有时候明明页面能找到元素,代码运行却找不到。 playwright 的 page.pause() 断点功能出现,让打开可以愉快的在页面上调试了,我们甚至可以直接使用 playwright.$(selector) 直接支持playwright选择器的方法。
2.1 page.pause() 断点
在代码中加入 page.pause() 进入断点状态
from playwright.sync_api import Playwright, sync_playwright, expect
def run(playwright: Playwright)-> None:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("http://127.0.0.1:8000/login.html")
page.get_by_placeholder("请输入用户名").click()
page.get_by_placeholder("请输入用户名").fill("yoyo")
page.get_by_placeholder("请输入密码").click()
page.get_by_placeholder("请输入密码").fill("aa123456")
page.pause() # 断点
page.get_by_role("button", name="立即登录 >").click()
# --------------------
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)
运行后会弹出 playwright inspector 工具
2.2 console 调试定位
我们可以在用代码打开的浏览器上f12 打开console页面,输入playwright.$(selector) 调试定位
2.3 playwright inspector 使用
还可以在playwright inspector 工具上点开启录制按钮,在页面上点点点,就可以生成对应的元 素和操作
点击 Pick locator 后在浏览器上选择需要定位的元素,即可生成对应的 locator
3. POM 设计模式
POM(Page Object Models) 页面对象模型已经成了写 web 自动化的一个标准模型。
页面对象代表 Web 应用程序的一部分。电子商务 Web 应用程序可能有一个主页、一个列表页 面和一个结帐页面。它们中的每一个都可以由页面对象模型表示。 页面对象通过创建适合您的应用程序的更高级别的 API 来简化创作,并通过在一个地方捕获元 素选择器和创建可重用代码来避免重复来简化维护。
3.1 页面封装元素定位和操作方法
class RegisterPage:
def __init__(self, page: Page):
self.page = page
self.locator_username = page.get_by_label("用 户 名:")
self.locator_password = page.get_by_label("密 码:")
self.locator_register_btn = page.locator('text=立即注册')
self.locator_login_link = page.locator('text=已有账号?点这登录')
# 用户名输入框提示语
self.locator_username_tip1 = page.locator('[data-fv-validator="notEmpty"][data-fv-for="username"]')
self.locator_username_tip2 = page.locator('[data-fv-validator="stringLength"][data-fv-for="username"]')
self.locator_username_tip3 = page.locator('[data-fv-validator="regexp"][data-fv-for="username"]')
# 密码输入框提示语
self.locator_password_tip1 = page.locator('[data-fv-validator="notEmpty"][data-fv-for="password"]')
self.locator_password_tip2 = page.locator('[data-fv-validator="stringLength"][data-fv-for="password"]')
self.locator_password_tip3 = page.locator('[data-fv-validator="regexp"][data-fv-for="password"]')
# 账号或密码不正确
self.locator_register_error = page.locator('text=用户名已存在或不合法!')
def navigate(self):
with allure.step("导航到注册页"):
self.page.goto("/register.html")
def fill_username(self, username):
with allure.step(f"输入用户名:{username}"):
self.locator_username.fill(username)
def fill_password(self, password):
with allure.step(f"输入密码:{password}"):
self.locator_password.fill(password)
def click_register_button(self):
with allure.step(f"点注册按钮"):
self.locator_register_btn.click()
def click_login_link(self):
with allure.step(f"点登录链接"):
self.locator_login_link.click()
3.2 用例设计
class TestRegister:
"""注册功能"""
@pytest.fixture(autouse=True)
def start_for_each(self, unlogin_page: Page):
"""
同登录功能用独立的上下文环境,不加载cookie
:param unlogin_page: 独立上下文
:return: None
"""
print("for each--start: 打开新页面访问注册页")
self.register = RegisterPage(unlogin_page)
self.register.navigate()
yield
print("for each--end: 后置操作")
def test_register_1(self):
"""用户名为空,点注册"""
self.register.fill_username('')
self.register.fill_password('123456')
self.register.click_register_button()
# 断言
expect(self.register.locator_username_tip1).to_be_visible()
expect(self.register.locator_username_tip1).to_contain_text("不能为空")
3.3 运行用例
使用pytest 命令行运行用例
pytest --alluredir ./report
查看报告
allure serve ./report
三.项目实战
项目结构
├─cases
│ │ test_env_list.py
│ │ test_login.py
│ │ test_project_list.py
│ │ test_register.py
│ │ __init__.py
│
├─mocks
│ │ __init__.py
│ │ mock_api.py
│
├─models
│ │ __init__.py
│ │ add_module_page.py
│ │ add_project_page.py
│ │ list_env_page.py
│ │ login_page.py
│ │ project_list_page.py
│ │ register_page.py
│
├─plugins
│ │ __init__.py
│ │ pytest_base_url_plugin.py
│ │ pytest_playwright.py
│
├─reports
├─test-results
│
├─conftest.py
├─pytest.ini
├─README.md
├─requirements.txt
├─run.py
1.pytest-playwright插件编写写测试用例
pytest-playwright插件完美的继承了pytest 用例框架和playwright基础使用的封装,基本能满足工 作中的常规需求了,不需要我们再做额外的插件开发。
代码示例:
import re
from playwright.sync_api import Page, expect
def test_homepage(page: Page):
page.goto("https://playwright.dev/")
# Expect a title "to contain" a substring.
expect(page).to_have_title(re.compile("Playwright"))
# create a locator
get_started = page.get_by_role("link", name="Get started")
# Expect an attribute "to be strictly equal" to the value.
expect(get_started).to_have_attribute("href", "/docs/intro")
# Click the get started link.
get_started.click()
# Expects the URL to contain intro.
expect(page).to_have_url(re.compile(".*intro"))
默认情况下,测试将在 chromium 上运行。这可以通过 CLI 选项进行配置。 测试以无头模式运行,这意味着在运行测试时不会打开浏览器 UI。测试结果和测试日志将显示在终端中。
如果想看到打开的浏览器运行,可以加上--headed 参数
pytest test_my_application.py --headed
Playwright 提供了 expect 将等待直到满足预期条件的功能。
import re
from playwright.sync_api import expect
expect(page).to_have_title(re.compile("Playwright"))
定位器是 Playwright 自动等待和重试能力的核心部分。定位器代表了一种随时在页面上查找元 素的方法,并用于对诸如.click .fill 等之类的元素执行操作。
from playwright.sync_api import expect
get_started = page.get_by_role("link", name="Get started") expect(get_started).to_have_attribute("href", "/docs/installation")
get_started.click()
Playwright Pytest 插件基于测试装置的概念,例如传递到您的测试中的内置页面装置。 由于浏览器上下文,页面在测试之间被隔离,这相当于一个全新的浏览器配置文件,每个测试都 会获得一个全新的环境,即使在单个浏览器中运行多个测试也是如此。
1.2 内置fixture
Function scope:
这些固定装置在测试功能中请求时创建,并在测试结束时销毁。
- context: 用于测试的新浏览器上下文。
- page: 用于测试的新浏览器页面。
Session scope:
这些固定装置在测试函数中请求时创建,并在所有测试结束时销毁。
- playwright: playwright实例。
- browser_type: 当前浏览器的BrowserType实例。
- browser:由 Playwright 启动的浏览器实例。
- browser_name: 浏览器名称作为字符串。
- browser_channel: 浏览器通道作为字符串。
- is_chromium, is_webkit, is_firefox: 相应浏览器类型的布尔值。
自定义fixture选项
对于browser和context ,使用以下fixture来定义自定义启动选项。
- browser_type_launch_args:覆盖browser_type.launch()的启动参数。它应该返回一个字典。
- browser_context_args:覆盖browser.new_context()的选项。它应该返回一个字典。
2.new_context上下文之 上下文之base_url 参数
如下测试场景, 在多个地方都会有访问的地址,并且环境地址都是一 样https://www.cnblogs.com, 也就是我们说的base_url地址。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
# 打开首页
page.goto("https://www.cnblogs.com/")
# 点点点后打开其他页
page.goto("https://www.cnblogs.com/yoyoketang")
context.close()
browser.close()
当很多地方都用到 base_url 的时候,为了方便切换环境,应该单独拿出来,做全局配置
base_url 参数是在 new_context() 新建上下文的时候使用
优化后的代码如下:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context(base_url='https://www.cnblogs.com')
page = context.new_page()
# 打开首页
page.goto("/")
# 点点点后打开其他页
page.goto("/yoyoketang")
context.close()
browser.close()
3.登录页面用例封装
3.1 Login页面封装
在 pages 目录创建 login_page.py, 封装登录页面抓取元素的属性,以及页面上操作方法
from playwright.sync_api import Page
class LoginPage:
def __init__(self, page: Page):
self.page = page
self.locator_username = page.get_by_label("用 户 名:")
self.locator_password = page.get_by_label("密
码:")
self.locator_login_btn = page.locator('text=立即登录')
self.locator_register_link = page.locator('text=没有账号?点这注册')
# 用户名输入框提示语
self.locator_username_tip1 = page\
.locator('[data-fv-validator="notEmpty"][data-fv-for="username"]')
self.locator_username_tip2 = page\
.locator('[data-fv-validator="stringLength"][data-fv-for="username"]')
self.locator_username_tip3 = page\
.locator('[data-fv-validator="regexp"][data-fv-for="username"]')
# 密码输入框提示语
self.locator_password_tip1 = page\
.locator('[data-fv-validator="notEmpty"][data-fv-for="password"]')
self.locator_password_tip2 = page\
.locator('[data-fv-validator="stringLength"][data-fv-for="password"]')
self.locator_password_tip3 = page\
.locator('[data-fv-validator="regexp"][data-fv-for="password"]')
# 账号或密码不正确!
self.locator_login_error = page\
.locator('text=账号或密码不正确!')
def navigate(self):
self.page.goto("/login.html")
def fill_username(self, username):
self.locator_username.fill(username)
def fill_password(self, password):
self.locator_password.fill(password)
def click_login_button(self):
self.locator_login_btn.click()
def click_register_link(self):
self.locator_register_link.click()
def login(self, username, password)-> None:
"""完整登录操作"""
self.locator_username.fill(username)
self.locator_password.fill(password)
self.locator_login_btn.click()
3.2 用例设计
用例的编写根据平常的功能测试用例去写
from pages.login_page import LoginPage
from playwright.sync_api import expect
import pytest
import allure
class TestLogin:
"""登录功能"""
@pytest.fixture(autouse=True)
def start_for_each(self, page):
print("for each--start: 打开新页面访问登录页")
self.login = LoginPage(page)
self.login.navigate()
yield
print("for each--end: 后置操作")
def test_login_1(self):
"""用户名为空,点注册"""
self.login.fill_username('')
self.login.fill_password('123456')
self.login.click_login_button()
# 断言
expect(self.login.locator_username_tip1)\
.to_be_visible()
expect(self.login.locator_username_tip1)\
.to_contain_text("不能为空")
def test_login_2(self):
"""用户名大于30字符"""
self.login.fill_username('hello world hello world hello world')
# 断言
expect(self.login.locator_username_tip2)\
.to_be_visible()
expect(self.login.locator_username_tip2)\
.to_contain_text("用户名称1-30位字符")
# 断言 登录按钮不可点击
expect(self.login.locator_login_btn).not_to_be_enabled()
def test_login_3(self):
"""用户名有特殊字符"""
self.login.fill_username('hello!@#')
# 断言
expect(self.login.locator_username_tip3).to_be_visible()
expect(self.login.locator_username_tip3)\
.to_contain_text("用户名称不能有特殊字符")
# 断言 注册按钮不可点击
expect(self.login.locator_login_btn).not_to_be_enabled()
@pytest.mark.parametrize("username,password,title",[
['yoyo', '12345678', '输入错误的密码'],
['yoyox1x2x3', '12345678', '输入错误的账号'],
])
def test_login_error(self, username, password, title):
"""登录失败场景"""
self.login.fill_username(username)
self.login.fill_password(password)
self.login.click_login_button()
# 断言提示语可见
expect(self.login.locator_login_error).to_be_visible()
def test_login_success(self):
"""登录正常流程, 登录成功"""
self.login.fill_username("yoyo")
self.login.fill_password('aa123456')
self.login.click_login_button()
# 断言页面跳转到首页
expect(self.login.page).to_have_title('首页')
expect(self.login.page).to_have_url('/index.html')
def test_login_success_2(self):
"""登录正常流程, 登录成功"""
self.login.fill_username("yoyo")
self.login.fill_password('aa123456')
# 显示断言重定向
with self.login.page.expect_navigation(url='**/index.html'):
self.login.click_login_button()
# 断言页面跳转到首页
expect(self.login.page).to_have_title('首页')
expect(self.login.page).to_have_url('/index.html')
def test_login_ajax(self):
"""登录正常流程, 获取异步ajax 请求"""
self.login.fill_username("yoyo")
self.login.fill_password('aa123456')
# 捕获ajax请求
with self.login.page.expect_request('**/api/login') as req:
self.login.click_login_button()
print(req.value) # 获取请求对象
# 断言请求内容
assert req.value.method == 'POST'
assert req.value.header_value('content-type') == 'application/json'
assert req.value.post_data_json == {
'username': 'yoyo', 'password': 'aa123456'
}
def test_login_ajax_response(self):
"""登录正常流程, 获取异步ajax 请求返回结果"""
self.login.fill_username("yoyo")
self.login.fill_password('aa123456')
# 捕获ajax请求
with self.login.page.expect_response('**/api/login') as res:
self.login.click_login_button()
print(res.value) # 获取请求对象
print(res.value.url)
print(res.value.status)
print(res.value.ok)
assert res.value.ok
assert res.value.status == 200
4. 断言登录成功
当页面登录成功的时候,会重定向到首页,这时候我们如何断言登录成功了?
登录成功我们有多种判断方式:
1.页面跳转到首页,直接断言首页的 title 和 url 地址是否正确
2.显示断言 expect_navigation ,判断导航到指定 url 地址
3.也可以断言 Ajax 请求,判断前端发过去的 Ajax 请求参数是否正确
4.还可以断言 Ajax 响应数据,判断接口响应是否正确
登录成功用例
- 页面跳转到首页,直接断言首页的 title 和 url 地址是否正确
- 显示断言你 expect_navigation ,判断导航到指定 url 地址
5.参数化场景
5.1 Pytest 参数化
pytest框架参数化的实现格式
import pytest
@pytest.mark.parametrize("test_input,expected",
[
["3+5", 8],
["2+4", 6],
["6 * 9", 42]
]
)
def test_eval(test_input, expected):
assert eval(test_input) == expected
5.2 用例参数化
登录失败的场景,输入用户名和密码不一样,得到的结果提示语也不一样
@pytest.mark.parametrize("username,password,title",[
['yoyo', '12345678', '输入错误的密码'],
['yoyox1x2x3', '12345678', '输入错误的账号'],
])
def test_login_error(self, username, password, title):
"""登录失败场景"""
self.login.fill_username('yoyo')
self.login.fill_password('12345678')
self.login.click_login_button()
# 断言提示语可见
expect(self.login.locator_login_error).to_be_visible()
6.断言Ajax异步请求
6.1 断言 ajax 异步请求
断言点登录按钮,发过去的请求参数与头部内容是否正确
def test_login_ajax(self):
"""登录正常流程, 获取异步ajax 请求"""
self.login.fill_username("yoyo")
self.login.fill_password('aa123456')
# 捕获ajax请求
with self.login.page.expect_request('**/api/login') as req:
self.login.click_login_button()
print(req.value) # 获取请求对象
# 断言请求内容
assert req.value.method == 'POST'
assert req.value.header_value('content-type') == 'application/json'
assert req.value.post_data_json == {
'username': 'yoyo', 'password': 'aa123456'
}
6.2 断言ajax 请求结果
断言点登录按钮,ajax 请求返回的结果是否正确
def test_login_ajax_response(self):
"""登录正常流程, 获取异步ajax 请求"""
self.login.fill_username("yoyo")
self.login.fill_password('aa123456')
# 捕获ajax请求
with self.login.page.expect_response('**/api/login') as res:
self.login.click_login_button()
print(res.value) # 获取返回对象
print(res.value.url)
print(res.value.status)
print(res.value.ok)
assert res.value.ok
assert res.value.status == 200
7. 解决反复登录问题
在 web 网站自动化中,很多用例场景都是需要先登录才能访问的(注册和登录页面除外)如果 把登录写到前置操作,就会导致重复登录问题。
前面说到可以用 cookies 的方式去登录,那么我们只需要在所有用例之前登录一次,保存 cookies 即可
全局仅运行一次,可以用 pytest 的 fixtures,设置 session 级别即可解决, 设置 autouse=True 它会在所以用例之前自动执行。 在 cases 目录创建 conftest.py 文件,作为整个项目用例的前置操作。
8.context加载cookies
在 context上下文添加参数,也就是前面 pytest-playwright 插件提到的, 重写 browser_context_args 覆盖原来的 browser_context_args 。
9.添加项目
新增项目界面只有一个 form 表单提交的功能,当新增成功,重定向到项目列表页面

9.1 封装页面面对象
先封装页面对象
from playwright.sync_api import Page
class AddProjectPage:
def __init__(self, page: Page):
self.page = page
self.locator_project_name = page.get_by_label("项目名称:")
self.locator_publish_app = page.get_by_label("所属应用:")
self.locator_project_desc = page.get_by_label('项目描述:')
self.locator_save_button = page.get_by_text('点击提交')
def navigate(self):
self.page.goto("/add_project.html")
def fill_project_name(self, name):
self.locator_project_name.fill(name)
def fill_publish_app(self, text):
self.locator_publish_app.fill(text)
def fill_project_desc(self, text):
self.locator_project_desc.fill(text)
def click_save_button(self):
self.locator_save_button.click()
9.2 完成用例编写
from pages.add_project_page import AddProjectPage
from playwright.sync_api import expect
import pytest
import uuid
class TestAddProject:
"""添加项目"""
@pytest.fixture(autouse=True)
def start_for_each(self, page):
print("for each--start: 打开添加项目页")
self.add_project = AddProjectPage(page)
self.add_project.navigate()
yield
print("for each--end: 后置操作")
@pytest.mark.parametrize("name, app, desc", [
["abc!@", "", ""],
["aaaaabbbbbcccccdddddeeeeefffff1", "", ""],
["abc", "aa!@", ""]
])
def test_add_project_disabled(self, name, app, desc):
"""异常场景-项目名称,无效等价:特殊字符/大于30个字符"""
self.add_project.fill_project_name(name)
self.add_project.fill_publish_app(app)
self.add_project.fill_project_desc(desc)
# 断言提交按钮状态 不可点击
expect(self.add_project.locator_save_button).to_be_disabled()
def test_add_project_null(self):
"""异常场景-项目名称不能为空"""
self.add_project.fill_project_name("")
self.add_project.fill_publish_app("")
self.add_project.fill_project_desc("")
self.add_project.click_save_button()
# 断言提交按钮状态 不可点击
expect(self.add_project.locator_save_button).to_be_disabled()
def test_add_project_success(self, page):
"""提交成功,跳转到项目列表"""
# 生成随机账号
self.add_project.fill_project_name(
str(uuid.uuid4()).replace('-', '')[:25]
)
self.add_project.fill_publish_app("xx")
self.add_project.fill_project_desc("xxx")
# 断言跳转到项目列表页
with page.expect_navigation(url="**/list_project.html"):
# 保存成功后,重定向到列表页
self.add_project.click_save_button()
9.3 提交成功判断新增项目在列表
table表格定位与内容获取,获取第3列的数据放到list列表,断言新增的名称在列表里

# 断言新增模块名称 "登录" 在列表里
loc_table_module = self.add_project.page.locator(
'//*[@id="table"]/tbody/tr/td[3]'
)
# 获取文本
all_module_names = [i.inner_text() for i in loc_table_module.all()]
print(f"获取页面 table 表格-模块名称列全部内容: {all_module_names}")
assert "登录" in all_module_names
10. Mock新增项目目400和和500异常场景
如果你们项目是前后端分离的,你只关注前端功能(后端的功能给接口自动化人员去做) 那么我们不用管接口的返回内容,也不用跟数据库打交道。直接 mock 自己需要的数据即可。 有些调用第三方接口,也可以用 mock 拦截掉。
10.1 mock 数据
import json
"""
/**** 模拟新增项目 返回 400 ***/
"""
mock_project_400 = {
"url": "**/api/project",
"handler": lambda route: route.fulfill(
status=400,
body=json.dumps({
"errors":
{
"project_name": "yo yo 已存在"
},
"message": "Input payload validation failed"
})
)
}
"""
/**** 模拟新增项目 返回 500 ***/
"""
mock_project_500 = {
"url": "**/api/project",
"handler": lambda route: route.fulfill(
status=500,
body="服务端错误"
)
}
10.2 mock用例场景
def test_add_project_400(self, page):
"""项目名称重复,弹出模态框"""
self.add_project.fill_project_name("yo yo")
self.add_project.fill_publish_app("xx")
self.add_project.fill_project_desc("xxx")
# mock 接口返回400
page.route(**mock_api.mock_project_400)
self.add_project.click_save_button()
# 校验结果 弹出框文本包含
expect(page.locator('.bootbox-body')).to_contain_text('已存在')
def test_add_project_500(self, page):
"""服务器异常 500 状态码"""
self.add_project.fill_project_name("test")
self.add_project.fill_publish_app("xx")
self.add_project.fill_project_desc("xxx")
# mock 接口返回500
page.route(**mock_api.mock_project_500)
self.add_project.click_save_button()
# 校验结果 弹出框文本包含
expect(page.locator('.bootbox-body')).to_contain_text('操作异常')
五. Allure报告


7676

被折叠的 条评论
为什么被折叠?



