Playwright浏览器上下文:多账号隔离测试与并行模拟实战

📅 2026/7/6 9:57:55
Playwright浏览器上下文:多账号隔离测试与并行模拟实战
1. 项目概述为什么我们需要浏览器上下文如果你做过Web自动化测试尤其是涉及登录态或者多账号的场景大概率遇到过这样的头疼事脚本里登录了一个账号A执行完操作后想换个账号B再跑一遍结果发现两个账号的数据串了或者B账号的操作直接让A账号掉线了。更麻烦的是有些测试需要模拟多个用户同时在线操作比如测试聊天室或者电商的秒杀场景一个浏览器实例根本搞不定。传统的解决方案是什么开多个浏览器进程或者用无痕模式来回切换。但这就像用蛮力解决问题笨重且低效。每个新浏览器进程都带着完整的用户数据、缓存、Cookie启动慢、占用资源多而且状态管理起来一团糟。这就是Playwright的Browser Context浏览器上下文要解决的核心问题。你可以把它理解为一个完全隔离的浏览器会话环境。每个上下文都拥有自己独立的Cookie、本地存储、缓存、甚至网络代理设置。它们共享同一个浏览器进程但彼此之间“老死不相往来”。这就像在一台电脑上为不同用户创建了多个独立的桌面账户每个账户有自己的文档和设置互不干扰。用Python配合Playwright实现多账号隔离测试其价值远不止于“不串号”。它使得并行测试变得极其轻量和高效可以在一台机器上轻松模拟成百上千个独立用户会话为压力测试、竞态条件测试、多租户SaaS应用测试提供了完美的底层支持。接下来我们就深入拆解看看如何用几行Python代码把这种强大的隔离能力玩转起来。2. 核心概念拆解Browser、Context、Page的关系与隔离原理在动手写代码之前必须理清Playwright中三个核心对象的关系这是避免后续踩坑的基础。很多人一开始会混淆Browser、Context和Page导致写出来的脚本要么性能低下要么无法实现真正的隔离。2.1 三层架构从进程到标签页你可以把这三者的关系想象成一个公司的组织结构Browser浏览器相当于公司总部。它是一个实际的浏览器进程如Chromium、Firefox。启动一个Browser是资源开销最大的操作。一个总部可以下设多个事业部。Context上下文相当于独立的事业部。每个事业部在总部大楼里运营但拥有独立的人事、财务、规章制度即独立的Cookie、LocalStorage、SessionStorage。事业部之间数据严格隔离。创建Context的成本远低于启动新的Browser。Page页面相当于事业部下的具体项目组。一个事业部Context可以同时开展多个项目多个Page即多个标签页。这些项目组共享事业部的资源同一个Cookie池。用代码来直观感受一下层级import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 启动“公司总部” - 开销大 browser await p.chromium.launch(headlessFalse) # 创建两个“独立事业部” context_a await browser.new_context() context_b await browser.new_context() # 在每个事业部里创建“项目组”页面 page_a1 await context_a.new_page() page_a2 await context_a.new_page() # 与page_a1共享context_a的Cookie page_b1 await context_b.new_page() # 与context_a完全隔离 # ... 执行操作 await browser.close() asyncio.run(main())2.2 隔离的深度到底什么被隔离了理解隔离的边界至关重要。Context的隔离是**会话级Session-level**的而非进程级。这意味着绝对隔离的资源包括Cookie与Web存储这是最核心的。在context_a中登录产生的Cookiecontext_b绝对无法读取。LocalStorage和SessionStorage同样如此。缓存每个Context拥有独立的磁盘和内存缓存。一个Context中加载过的资源不会给另一个Context带来加速但也避免了缓存污染。认证与权限HTTP基本认证、证书等状态是绑定到Context的。网络代理与请求改写你可以为每个Context单独设置代理服务器或请求拦截器route实现不同“用户”从不同IP访问。共享或可能产生影响的资源包括浏览器进程资源所有Context共享同一个浏览器进程的内存和CPU。如果一个Context内的页面发生内存泄漏或崩溃极端情况下可能影响整个浏览器进程的稳定性但Playwright的架构已极大降低了此风险。操作系统级别的限制如主机端口号、最大文件句柄数等。如果你创建了上千个Context可能会触及系统限制。实操心得不要把Context当成“万能隔离箱”。对于需要绝对进程隔离的场景例如测试浏览器插件冲突、完全独立的渲染环境你还是需要启动多个Browser实例。但对于99%的多账号、多会话测试Context级别的隔离已经绰绰有余且效率高出几个数量级。3. 环境准备与基础实战创建你的第一个隔离上下文理论讲完我们进入实战。假设我们要测试一个论坛系统需要同时用管理员账号和普通用户账号进行操作。3.1 环境搭建与Playwright安装首先确保你的Python环境建议3.8已经就绪。Playwright的安装是一站式的# 安装playwright的Python库 pip install playwright # 安装浏览器驱动Chromium, Firefox, WebKit playwright installplaywright install这一步会下载所需的浏览器二进制文件请确保网络通畅。国内用户如果遇到下载慢的问题可以设置环境变量PLAYWRIGHT_DOWNLOAD_HOST为国内镜像源例如export PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright/ playwright install chromium # 只安装Chromium也足够大多数场景3.2 同步 vs. 异步API的选择Playwright提供了同步和异步两套API。对于多账号隔离测试这种可能涉及并行操作的场景我强烈推荐使用异步APIasyncio。原因在于性能异步IO可以在单个线程内高效处理多个Context的等待操作如页面加载、网络请求更节省资源。自然表达并发asyncio.gather等语法能清晰地表达“同时操作多个账号”的意图。未来兼容性现代Python异步生态是主流。当然如果你对asyncio不熟悉或者脚本逻辑是严格的线性执行使用同步APIsync_playwright也完全没问题代码更直观。本文示例将以异步API为主因为这才是发挥Playwright并发威力的正确方式。3.3 基础模式顺序创建与使用隔离上下文我们先看一个最简单的模式顺序创建两个上下文分别登录不同账号。import asyncio from playwright.async_api import async_playwright async def test_isolated_accounts(): 基础示例顺序创建两个隔离的上下文模拟管理员和用户。 async with async_playwright() as p: # 1. 启动浏览器 browser await p.chromium.launch(headlessFalse) # 非无头模式方便观察 print(浏览器已启动) # 2. 创建第一个上下文 - 管理员环境 admin_context await browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ) admin_page await admin_context.new_page() await admin_page.goto(https://your-forum.com/login) # 执行管理员登录操作... await admin_page.fill(#username, admin) await admin_page.fill(#password, admin123) await admin_page.click(button[typesubmit]) print(管理员账号登录成功) # 此时admin_context 里已经存储了管理员的登录Cookie # 3. 创建第二个上下文 - 普通用户环境 (与admin_context完全隔离) user_context await browser.new_context() # 使用默认设置 user_page await user_context.new_page() await user_page.goto(https://your-forum.com/login) # 执行用户登录操作... await user_page.fill(#username, test_user) await user_page.fill(#password, user123) await user_page.click(button[typesubmit]) print(普通用户账号登录成功) # user_context 存储的是普通用户的Cookie与管理员无关 # 4. 验证隔离性在用户页面访问管理员后台应该失败或跳转 await user_page.goto(https://your-forum.com/admin) # 由于没有管理员Cookie页面应该显示无权限或跳回登录页 # 这里可以添加断言例如检查页面标题或URL page_title await user_page.title() assert Admin not in page_title and Login in page_title print(隔离性验证通过用户无法进入管理后台) # 5. 清理资源 (async with 语句会自动关闭这里是显式操作) await admin_context.close() await user_context.close() await browser.close() asyncio.run(test_isolated_accounts())关键点解析browser.new_context()是创建隔离环境的核心方法。你可以传入一个字典参数来配置这个新环境比如视口大小、User-Agent、语言偏好、权限地理位置、通知等。两个Contextadmin_context和user_context虽然共享同一个browser进程但它们的Cookie Jar是独立的。这是实现“多账号同时在线”的魔法所在。资源管理务必记得在结束时关闭context和browser。使用async with语句块是更好的选择它能确保即使发生异常资源也能被正确清理。4. 高级实战技巧复用、并行与状态管理基础模式只能算是热身。在实际项目中我们面临的场景要复杂得多如何高效管理上百个上下文如何复用登录状态避免每次重复登录如何真正并行执行测试下面我们来解决这些问题。4.1 上下文复用与持久化状态每次测试都重新登录账号太浪费时间。Playwright允许你将一个Context的状态主要是存储Storage保存到磁盘后续直接加载瞬间恢复登录态。import asyncio import os from pathlib import Path from playwright.async_api import async_playwright async def persist_and_reuse_context(): 演示如何保存和复用浏览器上下文状态。 适用于需要长期登录的测试账号如社交媒体、企业后台。 storage_state_path Path(./playwright_state/admin_state.json) async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) # 场景一首次登录并保存状态 if not storage_state_path.exists(): print(未发现持久化状态文件执行首次登录...) context await browser.new_context() page await context.new_page() await page.goto(https://your-app.com/login) # 模拟登录流程 await page.fill(#email, admincompany.com) await page.fill(#password, secure_password) await page.click(#remember-me) # 勾选“记住我” await page.click(button:has-text(Sign In)) await page.wait_for_url(**/dashboard) # 等待跳转到登录后页面 # 关键步骤将当前上下文的状态Cookie, LocalStorage保存到文件 await context.storage_state(pathstorage_state_path) print(f登录状态已保存至: {storage_state_path}) await context.close() # 场景二复用已保存的状态 print(发现持久化状态文件直接加载状态...) # 创建新上下文并直接加载之前保存的状态 context2 await browser.new_context(storage_statestorage_state_path) page2 await context2.new_page() await page2.goto(https://your-app.com/dashboard) # 验证页面应该直接显示登录后的仪表盘无需再次登录 try: await page2.wait_for_selector(#user-avatar, timeout5000) print(状态复用成功已处于登录状态。) except: print(状态可能已失效需要重新登录。) os.remove(storage_state_path) # 删除无效状态文件 await browser.close() asyncio.run(persist_and_reuse_context())注意事项storage_state保存的是静态的快照。如果网站的会话有过期时间如30天那么这个文件也会在30天后失效。对于需要高频使用的测试账号建议将状态保存逻辑与定期刷新逻辑结合或者将其作为测试前置准备阶段的一部分。4.2 实现真正的并行测试asyncio.gather 的威力顺序执行多个上下文操作无法模拟真正的并发用户行为。使用asyncio.gather可以轻松实现并行操作。import asyncio from playwright.async_api import async_playwright async def simulate_user_session(user_id, browser): 模拟一个独立用户的操作会话 # 为每个用户创建独立的上下文 context await browser.new_context( viewport{width: 1366, height: 768}, localezh-CN # 模拟中文用户 ) page await context.new_page() try: print(f用户 {user_id} 开始操作) await page.goto(https://your-ecommerce-site.com) await page.click(text登录) await page.fill(#username, ftest_user_{user_id}) await page.fill(#password, default_password) await page.click(button[typesubmit]) # 模拟一些用户行为例如浏览商品 await page.wait_for_selector(.product-list) products await page.query_selector_all(.product-item) if products: await products[user_id % len(products)].click() # 不同用户点击不同商品 await page.wait_for_timeout(1000) # 短暂停留 print(f用户 {user_id} 操作完成) except Exception as e: print(f用户 {user_id} 操作出错: {e}) finally: # 务必关闭上下文释放资源 await context.close() async def parallel_user_simulation(): 并行模拟多个用户同时操作 async with async_playwright() as p: # 启动浏览器。注意所有并行上下文共享这一个浏览器实例。 browser await p.chromium.launch(headlessTrue) # 定义要模拟的用户ID列表 user_ids [1, 2, 3, 4, 5] # 使用 asyncio.gather 并发执行多个用户会话 tasks [simulate_user_session(uid, browser) for uid in user_ids] await asyncio.gather(*tasks) # 这里是并发执行的关键 print(所有用户模拟完成) await browser.close() # 运行并行测试 asyncio.run(parallel_user_simulation())并发控制要点asyncio.gather会同时启动所有simulate_user_session协程。它们在同一线程内由事件循环调度在IO等待如网络请求、页面加载时切换从而实现高并发。虽然操作是并发的但创建太多Context比如超过100个可能会耗尽系统资源。需要根据机器性能CPU、内存和测试需求调整并发数。可以使用asyncio.Semaphore来限制最大并发数。每个Context内部的操作如点击、输入仍然是顺序执行的。真正的“同时点击”需要更复杂的同步机制但对于模拟用户负载这种并发模式已经非常有效。4.3 精细化配置为不同上下文定制环境不同的测试账号可能需要不同的“人设”。new_context()方法接受丰富的配置选项让你能精细控制每个隔离环境。import asyncio from playwright.async_api import async_playwright async def customized_contexts(): async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) # 上下文1模拟移动端用户 mobile_context await browser.new_context( **p.devices[iPhone 13 Pro Max], # 使用Playwright预置的设备描述符 localeen-US, timezone_idAmerica/Los_Angeles, permissions[geolocation], # 授予地理位置权限 geolocation{latitude: 34.0522, longitude: -118.2437}, # 洛杉矶坐标 color_schemedark # 偏好深色模式 ) # 上下文2模拟桌面端用户使用特定代理 desktop_context await browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, proxy{ server: http://my-proxy-server:8080 # 为该上下文设置代理 # username: user, # 如需认证 # password: pass }, ignore_https_errorsTrue, # 忽略HTTPS证书错误测试环境常用 java_script_enabledTrue # 可以控制JS是否执行 ) # 使用上下文 mobile_page await mobile_context.new_page() await mobile_page.goto(https://m.example.com) print(f移动端访问User-Agent: {await mobile_page.evaluate(navigator.userAgent)}) print(f地理位置: {await mobile_page.evaluate(() navigator.geolocation.getCurrentPosition(pos pos.coords))}) desktop_page await desktop_context.new_page() await desktop_page.goto(https://example.com) # 可以通过 desktop_page.request 等方法检查请求是否经过代理 await asyncio.sleep(2) await browser.close() asyncio.run(customized_contexts())配置项解读设备模拟p.devices提供了大量预置的设备配置如iPhone 13,Pixel 5一键模拟屏幕尺寸、UA、设备缩放比等对移动端测试极其友好。代理与网络proxy配置允许不同上下文通过不同代理IP访问这在测试地域限制功能或爬虫场景非常有用。ignore_https_errors在测试自签名证书的环境时是必备选项。权限与地理信息可以预设地理位置、通知、麦克风等权限模拟真实用户授权场景。java_script_enabled将其设为False可以模拟禁用JS的用户环境用于测试网站的可访问性Graceful Degradation。5. 工程化实践构建可维护的多账号测试框架当测试用例多起来后我们需要一个更结构化的方式来管理这些隔离的上下文和账号。下面分享一种我在实际项目中常用的模式。5.1 设计一个上下文管理器类将上下文的创建、配置、清理和账号绑定逻辑封装起来让测试用例更清晰。import asyncio from dataclasses import dataclass from typing import Optional, Dict, Any from playwright.async_api import Browser, BrowserContext, Page, async_playwright dataclass class TestAccount: 测试账号数据类 username: str password: str role: str # e.g., admin, vip_user, normal_user storage_state_path: Optional[str] None # 持久化状态文件路径 class IsolatedBrowserContextManager: 隔离的浏览器上下文管理器 def __init__(self, browser_type: str chromium, headless: bool True): self.browser_type browser_type self.headless headless self.browser: Optional[Browser] None self.context_pool: Dict[str, BrowserContext] {} # 账号角色 - 上下文映射 self._playwright None async def __aenter__(self): 异步上下文管理器入口 self._playwright await async_playwright().start() self.browser await getattr(self._playwright, self.browser_type).launch( headlessself.headless, args[--disable-blink-featuresAutomationControlled] # 隐藏自动化特征 ) return self async def __aexit__(self, exc_type, exc_val, exc_tb): 异步上下文管理器出口负责清理 for context in self.context_pool.values(): await context.close() if self.browser: await self.browser.close() if self._playwright: await self._playwright.stop() async def get_or_create_context(self, account: TestAccount) - Page: 获取或创建一个与账号绑定的隔离上下文及页面。 策略相同角色的账号可以复用上下文如果状态允许不同角色绝对隔离。 context_key account.role if context_key not in self.context_pool: # 创建新上下文 context_options { viewport: {width: 1920, height: 1080}, locale: zh-CN } # 如果账号有持久化状态则加载 if account.storage_state_path: context_options[storage_state] account.storage_state_path self.context_pool[context_key] await self.browser.new_context(**context_options) print(f创建了新的上下文用于角色: {account.role}) context self.context_pool[context_key] page await context.new_page() # 如果未提供持久化状态则执行登录流程 if not account.storage_state_path: await self._login_with_account(page, account) # 登录后可以选择保存状态 # state_path f./state_{account.role}_{account.username}.json # await context.storage_state(pathstate_path) # print(f已保存登录状态至: {state_path}) return page async def _login_with_account(self, page: Page, account: TestAccount): 通用的登录逻辑需根据实际被测系统调整 await page.goto(https://your-app.com/login) await page.fill(input[nameusername], account.username) await page.fill(input[namepassword], account.password) await page.click(button[typesubmit]) # 等待登录成功指示器 await page.wait_for_selector(.user-dashboard, timeout10000) print(f账号 {account.username} ({account.role}) 登录成功) # 使用示例 async def run_structured_test(): # 定义测试账号 accounts [ TestAccount(admintest.com, admin123, admin), TestAccount(vip_usertest.com, vip123, vip_user), TestAccount(normal_usertest.com, user123, normal_user), ] async with IsolatedBrowserContextManager(headlessFalse) as manager: tasks [] for account in accounts: # 为每个账号获取一个隔离的页面 task asyncio.create_task(manager.get_or_create_context(account)) tasks.append(task) pages await asyncio.gather(*tasks) # 现在 pages[0] 是管理员页面pages[1] 是VIP页面pages[2] 是普通用户页面 # 它们处于完全隔离的上下文中admin和normal_user隔离vip_user和normal_user隔离 # 并行执行各账号的测试操作 test_coroutines [] for i, page in enumerate(pages): test_coroutines.append(execute_user_test(page, accounts[i])) await asyncio.gather(*test_coroutines) async def execute_user_test(page: Page, account: TestAccount): 模拟执行某个用户的测试流程 print(f开始执行 {account.role} 的测试流程) # 这里编写具体的测试步骤例如 await page.goto(https://your-app.com/dashboard) # ... 更多操作 print(f{account.role} 测试流程执行完毕) if __name__ __main__: asyncio.run(run_structured_test())这个管理器的好处是角色隔离按账号角色role管理上下文相同角色的测试可以复用登录状态如果业务允许提升效率。资源管理使用异步上下文管理器__aenter__/__aexit__确保浏览器和所有上下文在测试结束后被正确关闭避免资源泄漏。状态持久化集成通过storage_state_path轻松集成状态保存与加载。可扩展性可以很容易地添加代理配置、自定义设备模拟、请求拦截等高级功能。5.2 与Pytest等测试框架集成在实际项目中我们通常使用Pytest来组织测试用例。将Playwright Context与Pytest Fixture结合是标准做法。# conftest.py import pytest import asyncio from playwright.async_api import Browser, BrowserContext, Page, async_playwright from typing import Generator, AsyncGenerator pytest.fixture(scopesession) def event_loop(): 为异步测试创建事件循环 loop asyncio.get_event_loop_policy().new_event_loop() yield loop loop.close() pytest.fixture(scopesession) async def browser() - AsyncGenerator[Browser, None]: 会话级Fixture启动浏览器 async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) yield browser await browser.close() pytest.fixture(scopefunction) # 每个测试函数一个独立的上下文实现用例隔离 async def isolated_context(browser: Browser) - AsyncGenerator[BrowserContext, None]: 函数级Fixture为每个测试用例创建独立的浏览器上下文 context await browser.new_context( viewport{width: 1920, height: 1080}, ignore_https_errorsTrue ) yield context await context.close() pytest.fixture(scopefunction) async def admin_page(isolated_context: BrowserContext) - Page: 基于独立上下文创建一个已登录的管理员页面 page await isolated_context.new_page() # 执行管理员登录逻辑 await page.goto(LOGIN_URL) await page.fill(#username, ADMIN_USER) await page.fill(#password, ADMIN_PASS) await page.click(button[typesubmit]) await page.wait_for_selector(#admin-navbar) yield page # 页面会自动随上下文关闭而关闭 pytest.fixture(scopefunction) async def user_page(isolated_context: BrowserContext) - Page: 基于另一个独立上下文创建一个已登录的普通用户页面 # 注意这个isolated_context是新的与admin_page的上下文不同 page await isolated_context.new_page() await page.goto(LOGIN_URL) await page.fill(#username, TEST_USER) await page.fill(#password, TEST_PASS) await page.click(button[typesubmit]) await page.wait_for_selector(.user-menu) yield page # test_multi_account.py import pytest pytest.mark.asyncio async def test_admin_can_manage_users(admin_page: Page): 测试管理员功能 await admin_page.goto(/admin/users) # 断言管理员能看到用户管理界面 assert await admin_page.is_visible(table.user-list) # 执行管理操作... pytest.mark.asyncio async def test_user_cannot_access_admin_area(user_page: Page): 测试普通用户权限隔离 await user_page.goto(/admin/users) # 断言普通用户会被重定向或无权限提示 assert await user_page.is_visible(textAccess Denied) or /login in user_page.url通过Pytest Fixture我们实现了用例级隔离每个测试函数都获得全新的Browser Context测试之间完全不会相互干扰。状态封装将登录等前置操作封装在Fixture里测试用例只需关注业务逻辑。灵活组合可以轻松创建出admin_page和user_page在同一个测试函数中同时使用它们来模拟交互场景虽然它们来自不同Context。6. 常见问题、性能调优与排查技巧即使掌握了上面的所有技巧在实际使用中你还是会遇到各种问题。下面是我从大量实战中总结出的“避坑指南”。6.1 常见问题与解决方案速查表问题现象可能原因解决方案Cookie看似“泄漏”测试代码中意外共享了同一个Page或Context对象。仔细检查代码确保每个账号操作的是从browser.new_context()获得的全新Context下的Page。使用IDE的调试器检查对象ID。并行测试时页面互相干扰并行任务中对同一个Context下的多个Page进行了非原子操作。确保每个并发任务使用独立的Context。如果必须在同一Context下多Page并行需要对共享资源的操作加锁asyncio.Lock。内存占用过高最终崩溃创建的Context或Page过多且未及时关闭。1. 使用async with或确保finally块中调用close()。2. 控制并发数使用信号量asyncio.Semaphore。3. 定期清理不再使用的Contextawait context.close()。登录状态无法保持1. 网站使用Token而非Cookie认证。2. 网站有额外的安全策略如IP绑定。3.storage_state保存的时机不对在登录完成前。1. 手动拦截请求将Token注入到后续请求头中。2. 为固定账号绑定固定代理IP。3. 确保在登录成功并跳转到目标页后如wait_for_url再保存状态。移动端模拟不准确仅设置了视口和UA未模拟触摸事件、设备像素比等。使用p.devices预置描述符它包含了完整的设备模拟参数。手动设置has_touch: true。“检测到自动化工具”网站反爬或反自动化机制检测到了Playwright特征。1. 启动浏览器时添加参数args[--disable-blink-featuresAutomationControlled]。2. 使用context.add_init_script()注入脚本覆盖navigator.webdriver等属性。3. 考虑使用playwright-stealth等第三方库。6.2 性能调优建议浏览器启动模式如果不需要观察UI务必使用headlessTrue无头模式。这是最大的性能提升点。上下文复用策略对于登录成本高、会话长的账号使用storage_state持久化状态并复用Context。对于一次性或短会话测试每次创建新的Context更干净。并发数控制不要盲目创建成百上千个Context。使用asyncio.Semaphore限制最大并发数例如20-50取决于机器配置。监控内存和CPU使用情况。semaphore asyncio.Semaphore(20) # 最大并发20 async def limited_task(user_id): async with semaphore: await simulate_user_session(user_id, browser)资源清理这是防止内存泄漏的关键。确保每个Context和Page在使用后都被关闭。async with语句块是最可靠的方式。禁用不必要的资源加载如果测试不关心图片、样式、字体可以拦截并中止这些请求大幅提升页面加载速度。async def route_handler(route): if route.request.resource_type in [image, stylesheet, font]: await route.abort() else: await route.continue_() await context.route(**/*, route_handler)6.3 调试与排查技巧录制与回放对于复杂的用户操作流先用Playwright Code Generatorplaywright codegen录制再将其融入你的多上下文测试脚本中。视频与追踪在创建Context时启用视频录制和追踪对于排查偶发问题极其有用。context await browser.new_context( record_video_dir./videos/, # 录制视频 record_har_path./hars/test.har # 保存所有网络请求的HAR文件 )控制台日志将浏览器控制台日志重定向到你的Python日志系统方便调试JS错误。page.on(console, lambda msg: print(fCONSOLE: {msg.type}: {msg.text})) page.on(pageerror, lambda err: print(fPAGE ERROR: {err}))慢动作模式在调试时使用slow_mo参数让Playwright以慢速执行操作方便观察。browser await p.chromium.launch(headlessFalse, slow_mo100) # 每个操作延迟100毫秒多账号隔离测试的核心在于深刻理解Browser Context作为独立沙盒的本质并善用Python的异步并发能力来管理和调度这些沙盒。从简单的顺序隔离到复杂的并行模拟和状态持久化Playwright提供了一套强大而灵活的工具。将这些工具与良好的编程实践如封装、资源管理结合你就能构建出稳定、高效且易于维护的自动化测试体系从容应对各种复杂的多用户场景测试挑战。