前言
pytest-playwright 插件可以让我们快速编写pytest格式的测试用例,它提供了一个内置的page 对象,可以直接打开页面操作。
但是有时候我们需要2个账号是操作业务流程,比如A账号创建了一个任务,需要用到B账号去操作审批动作等。
如果需要2个账号同时登录,可以使用context 上下文,它可以做到环境隔离。
context 上下文环境隔离
使用 Playwright 编写的测试在称为浏览器上下文的隔离的全新环境中执行。这种隔离模型提高了可重复性并防止级联测试失败。
什么是测试隔离
测试隔离是指每个测试与另一个测试完全隔离。每个测试都独立于任何其他测试运行。这意味着每个测试都有自己的本地存储、会话存储、cookie 等。Playwright 使用BrowserContext实现了这一点,这相当于隐身式配置文件。它们的创建速度快、成本低,并且完全隔离,即使在单个浏览器中运行也是如此。Playwright 为每个测试创建一个上下文,并在该上下文中提供一个默认页面。
为什么测试隔离很重要
- 没有失败结转。如果一个测试失败,它不会影响另一个测试。
- 易于调试错误或不稳定,因为您可以根据需要多次运行单个测试。
- 并行运行、分片等时不必考虑顺序。
测试隔离有两种不同的策略:从头开始或在两者之间进行清理。在测试之间清理的问题是很容易忘记清理,有些东西是不可能清理的,比如“访问过的链接”。来自一个测试的状态可能会泄漏到下一个测试中,这可能会导致您的测试失败并使调试变得更加困难,因为问题来自另一个测试。从头开始意味着一切都是新的,因此如果测试失败,您只需查看该测试即可进行调试。
Playwright 如何实现测试
Playwright 使用浏览器上下文来实现测试隔离。每个测试都有自己的浏览器上下文。每次运行测试都会创建一个新的浏览器上下文。使用 Playwright 作为测试运行程序时,默认情况下会创建浏览器上下文。否则,您可以手动创建浏览器上下文。
browser = playwright.chromium.launch()
context = browser.new_context()
page = context.new_page()
浏览器上下文还可用于模拟涉及移动设备、权限、区域设置和配色方案的多页面场景
Playwright 可以在一个场景中创建多个浏览器上下文。当您想测试多用户功能(如聊天)时,这很有用。
from playwright.sync_api import sync_playwright
# 上海悠悠 wx:283340479
# blog:https://www.cnblogs.com/yoyoketang/
def run(playwright):
# create a chromium browser instance
chromium = playwright.chromium
browser = chromium.launch()
# create two isolated browser contexts
user_context = browser.new_context()
admin_context = browser.new_context()
# create pages and interact with contexts independently
with sync_playwright() as playwright:
run(playwright)
关于context上下文的详细介绍参考这篇https://www.h3blog.com/article/439/
多账号登录解决方案
pytest-playwright 插件默认有一个context 和page 的fixture
可以用pytest-playwright 插件自带的page对象,先登录用户A
用户B的登录,重新创建另外一个上下文环境
conftest.py
import pytest
from pages.login_page import LoginPage
"""
全局默认账号使用 "yoyo", "******" 在cases 目录的conftest.py 文件下
涉及多个账号切换操作的时候
我们可以创建新的上下文,用其它账号登录
"""
# 上海悠悠 wx:283340479
# blog:https://www.cnblogs.com/yoyoketang/
@pytest.fixture(scope="session")
def save_admin_cookies(browser, base_url, pytestconfig):
"""
admin 用户登录后保存admin.json cookies信息
:return:
"""
context = browser.new_context(base_url=base_url, no_viewport=True)
page = context.new_page()
LoginPage(page).navigate()
LoginPage(page).login("admin", "***********")
# 等待登录成功页面重定向
page.wait_for_url(url='**/index.html')
# 保存storage state 到指定的文件
storage_path = pytestconfig.rootpath.joinpath("auth/admin.json")
context.storage_state(path=storage_path)
context.close()
@pytest.fixture(scope="module")
def admin_context(browser, base_url, pytestconfig):
"""
创建admin上下文, 加载admin.json数据
:return:
"""
context = browser.new_context(
base_url=base_url,
no_viewport=True,
storage_state=pytestconfig.rootpath.joinpath("auth/admin.json"),
)
yield context
context.close()
运行完成后,会生成第二个账号对应的admin.json数据
在用例中,我们传admin_context 参数就可以得到B账号的上下文context对象了,基于context对象创建page页面对象
"""
整个项目中的上下文对象
page 用例中直接传page,默认使用登录后的context上下文创建的page对象
admin_context 针对admin用户登录后的上下文环境
"""
from playwright.sync_api import BrowserContext, Page
import pytest
import uuid
from pages.add_project_page import AddProjectPage
from pages.project_list_page import ProjectListPage
# 上海悠悠 wx:283340479
# blog:https://www.cnblogs.com/yoyoketang/
class TestMoreAccounts:
"""多账号切换操作示例"""
@pytest.fixture(autouse=True)
def start_for_each(self, page: Page, admin_context: BrowserContext):
print("for each--start: 打开添加项目页")
# 用户1
self.user1_project = AddProjectPage(page)
self.user1_project.navigate()
# 用户2
page2 = admin_context.new_page()
self.user2_project = ProjectListPage(page2)
self.user2_project.navigate()
yield
print("for each--end: 后置操作")
page.close()
page2.close()
def test_delete_project(self):
"""
测试流程:
step--A账号登录,创建项目xxx
step--B账号登录,删除项目xxx
:return:
"""
# 账号 1 添加项目
test_project_name = str(uuid.uuid4()).replace('-', '')[:25]
self.user1_project.fill_project_name(test_project_name)
self.user1_project.fill_publish_app("xx")
self.user1_project.fill_project_desc("xxx")
# 断言跳转到项目列表页
with self.user1_project.page.expect_navigation(url="**/list_project.html"):
# 保存成功后,重定向到列表页
self.user1_project.click_save_button()
# 账号 2 操作删除
self.user2_project.search_project(test_project_name)
with self.user2_project.page.expect_request("**/api/project**"):
self.user2_project.click_search_button()
self.user2_project.page.wait_for_timeout(3000)
self.user2_project.locator_table_delete.click()
# 确定删除
with self.user2_project.page.expect_response("**/api/project**") as resp:
self.user2_project.locator_boot_box_accept.click()
# 断言删除成功
resp_obj = resp.value
assert resp_obj.status == 200
文章转自:https://www.cnblogs.com/yoyoketang/p/17295940.html