前言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.cnblogs.com/yoyoketang/p/17142642.html 多账号登录解决方案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
|