1. 项目概述为什么是Playwright如果你正在寻找一个能横跨现代Web应用所有主流浏览器Chromium、Firefox、WebKit的自动化测试或爬虫工具并且厌倦了Selenium的配置繁琐和Puppeteer的浏览器单一性那么Playwright绝对值得你投入时间。它由微软团队开发原生支持Python、Node.js、.NET和Java其设计哲学从一开始就瞄准了现代Web的复杂性——单页应用、Shadow DOM、网络拦截、文件上传、地理位置模拟这些在传统工具里需要大量胶水代码才能实现的功能Playwright都提供了优雅的一站式解决方案。我最初接触Playwright是为了解决一个棘手的E2E测试需求一个重度依赖WebSocket和动态加载的React应用。用Selenium写出来的脚本既不稳定又冗长而Playwright的wait_for_load_state(networkidle)和内置的自动等待机制让脚本的健壮性直接上了一个台阶。更重要的是它的Python API设计得非常符合直觉即使你之前没有浏览器自动化的经验也能快速上手写出可用的脚本。这篇文章我将带你从零开始深入Playwright的每一个核心角落并通过几个实战项目让你不仅能“会用”更能“用好”。2. 环境搭建与核心概念解析2.1 一站式环境配置指南Playwright的安装比它的前辈们要友好得多。它自带浏览器内核无需单独配置WebDriver。但为了获得最佳开发体验我建议按以下步骤搭建环境。首先确保你有一个干净的Python环境。我强烈推荐使用venv创建虚拟环境避免包依赖冲突。# 创建项目目录并进入 mkdir playwright-project cd playwright-project # 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate接下来安装Playwright。这里有两个选择只安装库或者连同常用的浏览器一起安装。对于新手我推荐一次性装好。# 安装playwright库 pip install playwright # 安装Playwright所需的浏览器Chromium, Firefox, WebKit playwright install注意playwright install命令会下载大约几百MB的浏览器二进制文件到缓存目录。如果网络环境不佳可以尝试使用--with-deps参数或设置镜像源。有时公司内网可能会拦截这些下载你需要确认是否有相应的网络策略。安装完成后一个简单的验证脚本能帮你检查一切是否就绪。# test_install.py import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 尝试启动Chromium浏览器 browser await p.chromium.launch(headlessFalse) # headlessFalse让我们能看到浏览器 page await browser.new_page() await page.goto(https://example.com) print(f页面标题: {await page.title()}) await browser.close() asyncio.run(main())运行这个脚本如果能看到一个浏览器窗口弹出并访问example.com然后在控制台打印出标题那么恭喜你环境配置成功。2.2 同步与异步API如何选择Playwright提供了两套API同步和异步。这是初学者最容易困惑的点之一。同步API (playwright.sync_api)代码写起来是线性的更符合传统脚本的阅读习惯。Playwright在背后通过智能等待帮你处理了大多数异步操作让你感觉像是在写同步代码。适合大多数自动化脚本、爬虫和简单的测试场景。它的代码看起来像这样from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch() page browser.new_page() page.goto(https://example.com) # 看起来是同步调用实际内部会等待加载完成 print(page.title()) browser.close()异步API (playwright.async_api)显式使用async/await语法。当你的操作需要处理大量并发任务时例如同时监控多个页面、并行执行一组测试异步API能极大提升效率充分利用CPU资源。适合高性能爬虫、复杂的并发测试套件。import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: browser await p.chromium.launch() page await browser.new_page() await page.goto(https://example.com) print(await page.title()) await browser.close() asyncio.run(main())我的选择建议如果你是新手或者项目不涉及复杂并发从同步API开始。它的心智负担更小调试也更直观。当你需要同时操作多个页面或标签页并且感受到性能瓶颈时再考虑迁移到异步API。本文后续示例将主要使用同步API因为其受众更广但会在关键部分指出异步的写法。2.3 核心对象模型Browser, Context, Page理解Playwright的三个核心对象是写出高效脚本的关键。它们的关系就像浏览器-用户会话-单个标签页。Browser代表一个浏览器实例。你可以把它想象成你电脑上安装的Chrome或Firefox应用程序。通过launch()方法启动可以配置无头模式、代理、窗口大小等。browser p.chromium.launch(headlessFalse, slow_mo50) # slow_mo让每个操作延迟50毫秒方便观察Context浏览器上下文。这是一个独立的会话类似于一个隐身窗口。每个Context拥有独立的cookie、本地存储和缓存但共享同一个Browser进程。这是Playwright非常强大的一个特性你可以用它来模拟多个互不干扰的用户同时登录同一个网站。# 创建两个独立的上下文模拟两个用户 context1 browser.new_context() context2 browser.new_context(user_agent自定义UA)Page页面。代表一个标签页或一个弹出窗口。我们绝大部分的交互点击、输入、获取内容都发生在Page对象上。page context.new_page() page.goto(https://www.baidu.com)为什么Context如此重要假设你要测试一个购物车的并发修改。你可以创建两个Context每个Context里登录不同的用户账号然后同时操作同一个商品库存。它们完全隔离不会因为Cookie串了而导致测试失败。在爬虫场景中你可以用不同的Context来管理不同的会话状态比如一个Context负责抓取公开信息另一个Context负责维持已登录状态去抓取会员数据。3. 元素定位与交互精准操作的基石定位不到元素是自动化脚本失败的首要原因。Playwright提供了丰富且强大的定位器Locator其设计理念是“尽可能自动等待元素可用”。3.1 定位器Locator最佳实践Playwright推荐使用page.locator(selector)来创建定位器。这个定位器是“惰性”的只有在真正执行操作如click()时它才会去查找元素并且自带自动等待直到元素出现在DOM中并处于可操作状态如可见、可点击。最常用的定位策略按文本定位这是Playwright的一大亮点非常符合直觉。# 点击文本为“登录”的按钮 page.locator(text登录).click() # 点击包含“提交”文本的元素 page.locator(text提交).click() # 更精确的全文匹配 page.locator(textSign in).click()CSS选择器基础且强大与你在前端开发中使用的完全一致。page.locator(button#submit).click() page.locator(.list-item:has-text(Item 1)).click()XPath在CSS选择器无能为力时如基于复杂层级关系或文本内容XPath是最后的武器。但应谨慎使用因为它通常更脆弱。page.locator(xpath//button[contains(class, \primary\)]).click()按角色Role定位这是遵循WAI-ARIA标准的最佳实践对于现代组件库如Material-UI, Ant Design构建的页面尤其稳定。page.locator(rolebutton[name搜索]).click() page.locator(roletextbox).fill(关键字)我的实战心得定位策略的优先级应该是Role Text CSS XPath。优先使用Role和Text因为它们最贴近用户视角即使前端代码的CSS类名或ID改变了只要按钮文本或语义角色不变你的脚本就依然有效。编写选择器时尽量让它具有描述性且尽可能短。避免使用像div div:nth-child(3) span a这样冗长且脆弱的路径。3.2 高级交互不仅仅是点击和输入掌握了定位我们来看看Playwright如何模拟复杂的用户交互。处理下拉框Select 传统方式需要点击下拉框再点击选项。Playwright提供了更直接的方法。# 通过value选择 page.locator(select#country).select_option(cn) # 通过标签文本选择 page.locator(select#country).select_option(label中国) # 如果是自定义的下拉组件非原生select则需要模拟点击 page.locator(.custom-select-trigger).click() page.locator(.dropdown-item:has-text(北京)).click()文件上传 Playwright让文件上传变得极其简单无需模拟复杂的input typefile的点击事件。# 假设有一个 input typefile page.locator(input[typefile]).set_input_files(/path/to/my/file.pdf) # 上传多个文件 page.locator(input[typefile]).set_input_files([file1.pdf, file2.jpg]) # 移除已选择的文件 page.locator(input[typefile]).set_input_files([])鼠标与键盘操作# 鼠标悬停 page.locator(.menu-item).hover() # 右键点击 page.locator(.element).click(buttonright) # 双击 page.locator(.icon).dblclick() # 模拟键盘输入如按Tab键切换焦点按Enter提交 page.locator(input#username).press(Tab) page.locator(input#password).press(Enter) # 组合键如CtrlC page.keyboard.press(ControlC)拖放操作# 将source元素拖放到target元素上 source page.locator(#draggable) target page.locator(#droppable) source.drag_to(target)重要提示Playwright的自动等待机制在大多数情况下能保证元素可交互。但如果你遇到“元素不可点击”的错误除了检查选择器是否正确还可以尝试使用locator.wait_for(stateattached或visible)显式等待。在操作前先滚动元素到视图中locator.scroll_into_view_if_needed()。检查是否有模态框、遮罩层覆盖了目标元素。4. 等待、断言与网络控制4.1 智能等待告别sleep和timeout硬编码time.sleep(10)是自动化脚本的“癌症”——它不可靠且低效。Playwright内置了智能等待理解这些等待条件至关重要。导航等待page.goto()和page.click()如果点击会触发导航默认会等待页面触发load事件。对于单页应用这通常不够。# 等待直到网络几乎空闲至少500ms没有网络请求 page.goto(https://spa.example.com, wait_untilnetworkidle) # 等待直到DOMContentLoaded事件 page.goto(https://example.com, wait_untildomcontentloaded)元素等待定位器操作click,fill会自动等待元素可操作。# 下面的click会一直等待直到#button元素可见、可点击最多等待30秒默认超时时间 page.locator(#button).click() # 你可以自定义超时时间 page.locator(#slow-button).click(timeout10000) # 等待10秒显式等待使用page.wait_for_*系列函数。# 等待某个选择器对应的元素出现 page.wait_for_selector(.success-message) # 等待某个URL常用于表单提交后的跳转 page.wait_for_url(**/dashboard) # 等待一个函数在页面上下文中返回真值 page.wait_for_function(window.status ready)我的避坑经验wait_untilnetworkidle在动态加载内容非常多的页面上可能会等待过久甚至超时。一个更稳健的策略是结合使用wait_for_selector等待某个标志性元素如加载完成的占位符消失、主要内容区域出现的出现。例如page.wait_for_selector(.product-list, statevisible)。4.2 断言验证脚本行为的正确性Playwright Test一个基于Playwright的测试运行器提供了丰富的断言。但即使在不使用测试框架的纯脚本中我们也可以利用Python自带的assert或Playwright的期望ExpectAPI来进行验证。# 使用Python assert title page.title() assert Dashboard in title, f期望标题包含Dashboard实际是{title} # 使用Playwright的期望API更强大错误信息更友好 from playwright.sync_api import expect expect(page).to_have_title(My Page) expect(page.locator(.status)).to_have_text(Success) expect(page.locator(list-item)).to_have_count(10)4.3 拦截与模拟网络请求这是Playwright作为爬虫和测试利器的核心功能。你可以监听、修改甚至伪造任何网络请求和响应。监听请求/响应# 打印所有请求的URL page.on(request, lambda request: print(f {request.method} {request.url})) # 打印所有响应的状态码和URL page.on(response, lambda response: print(f {response.status} {response.url}))拦截并修改请求# 阻止对图片资源的请求以加速 page.route(**/*.{png,jpg,jpeg}, lambda route: route.abort()) # 修改请求头例如添加一个认证令牌 page.route(**/api/**, lambda route: route.continue_(headers{**route.request.headers, Authorization: Bearer token})) # 直接返回一个模拟的响应不发送真实请求 page.route(https://api.example.com/data, lambda route: route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({mock: data}) ))实战场景性能测试通过拦截和延迟响应route.continue_(delay1000)来模拟慢速网络。爬虫拦截API接口的响应直接提取JSON数据比从渲染后的HTML中解析要高效和准确得多。测试模拟后端API失败返回500错误或返回特定数据来测试前端应用的错误处理和数据渲染逻辑。5. 实战项目一构建一个健壮的Web爬虫让我们用一个实战项目来巩固以上知识。目标是爬取一个模拟的电商网站商品列表和详情该网站采用JavaScript动态加载数据。5.1 项目分析与设计目标网站https://demo-webstore.com任务遍历所有分类页面。获取每个分类下的商品列表名称、价格、链接。进入每个商品详情页抓取描述、规格等详细信息。处理分页。将数据保存为JSON文件。挑战商品列表是滚动加载无限滚动分类和商品详情数据通过API异步获取。设计思路使用一个Browser Context。主要使用同步API逻辑清晰。通过监听网络请求直接捕获API返回的JSON数据避免解析HTML。使用page.evaluate()执行JavaScript来触发滚动模拟用户行为加载更多商品。5.2 核心代码实现import json from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError def scrape_webstore(): all_products [] with sync_playwright() as p: # 启动浏览器设置视窗大小 browser p.chromium.launch(headlessTrue) # 生产环境用无头模式 context browser.new_context(viewport{width: 1920, height: 1080}) page context.new_page() # 监听特定的API响应直接捕获数据 def handle_api_response(response): if /api/products in response.url and response.status 200: try: # 尝试解析JSON数据 products_data response.json() # 假设API返回一个包含items字段的JSON if items in products_data: print(f捕获到 {len(products_data[items])} 个商品) all_products.extend(products_data[items]) except: print(f解析API响应失败: {response.url}) page.on(response, handle_api_response) # 1. 导航到首页 page.goto(https://demo-webstore.com, wait_untilnetworkidle) # 2. 获取所有分类链接 category_links page.locator(nav.categories a).all() category_urls [link.get_attribute(href) for link in category_links] category_urls [fhttps://demo-webstore.com{url} for url in category_urls if url] for category_url in category_urls[:2]: # 演示只爬前两个分类 print(f正在爬取分类: {category_url}) page.goto(category_url, wait_untildomcontentloaded) # 3. 处理滚动加载无限滚动 last_height page.evaluate(document.body.scrollHeight) scroll_attempts 0 max_attempts 5 # 最多滚动5次防止无限循环 while scroll_attempts max_attempts: # 滚动到底部 page.evaluate(window.scrollTo(0, document.body.scrollHeight)) # 等待新内容加载 page.wait_for_timeout(2000) # 等待2秒网络空闲等待可能不适用于滚动加载 # 检查是否滚动到了底部 new_height page.evaluate(document.body.scrollHeight) if new_height last_height: # 高度未变可能已加载完毕或遇到“加载更多”按钮 load_more_button page.locator(button:has-text(Load More)) if load_more_button.count() 0 and load_more_button.is_visible(): load_more_button.click() page.wait_for_timeout(3000) else: break # 没有更多内容了 last_height new_height scroll_attempts 1 # 4. 此时通过监听器商品列表数据应已捕获到all_products中 # 我们可以进一步访问每个商品详情页如果需要额外信息 # 这里为了演示我们只打印已捕获的商品数量 print(f分类 {category_url} 捕获到 {len([p for p in all_products if p.get(category) category_url])} 个商品) # 5. 去重并保存数据 # 简单的基于ID的去重 unique_products {prod[id]: prod for prod in all_products}.values() with open(products.json, w, encodingutf-8) as f: json.dump(list(unique_products), f, ensure_asciiFalse, indent2) print(f爬取完成共获取 {len(unique_products)} 个唯一商品。) browser.close() if __name__ __main__: scrape_webstore()5.3 爬虫项目优化与问题排查常见问题与解决方案被网站检测到是自动化脚本症状访问被拒绝、出现验证码、页面布局异常。对策使用context.new_context()时传入user_agent参数模拟真实浏览器。启用浏览器上下文中的viewport设置一个常见的分辨率。避免过快的操作在关键步骤间使用page.wait_for_timeout(1000-2000)加入随机延迟。高级使用playwright-stealth等插件尝试规避检测需谨慎可能违反网站条款。网络监听捕获不到数据症状handle_api_response函数从未被触发。排查在监听前先导航到页面监听器需要在导航之前添加。API的URL模式是否匹配使用print(response.url)调试。数据是否不是通过XHR/Fetch而是WebSocket或SSEPlaywright也支持WebSocket监听但处理更复杂。page.evaluate执行JavaScript出错症状控制台报JavaScript错误。对策确保传入evaluate的字符串是有效的JS代码。对于复杂操作可以定义一个JS函数字符串。传递参数时使用page.evaluate((arg) {...}, arg)格式。性能优化技巧并发爬取对于独立的分类页面可以使用异步APIasyncio.gather同时打开多个Page进行爬取大幅提升速度。资源拦截在爬虫场景下可以拦截不必要的资源如图片、样式表、字体、媒体文件以节省带宽和加速页面加载。def route_handler(route): if route.request.resource_type in [image, stylesheet, font, media]: route.abort() else: route.continue_() page.route(**/*, route_handler)合理使用无头模式headlessTrue默认不显示GUI资源占用更少速度更快。6. 实战项目二实现端到端E2E自动化测试Playwright最初就是为了测试而生的。我们用它来为一个简单的登录流程编写一个E2E测试脚本。6.1 测试场景设计测试应用一个虚构的博客平台登录页 (https://demo-blog.com/login)。 测试用例使用正确用户名和密码登录验证跳转到仪表盘。使用错误密码登录验证显示错误提示信息。测试“记住我”复选框功能。测试忘记密码链接是否有效。我们将使用Playwright的同步API和pytest测试框架来组织代码这是社区的主流选择。6.2 测试代码结构与实现首先安装pytest和pytest-playwright插件它提供了有用的Fixture。pip install pytest pytest-playwright创建测试文件test_login.pyimport pytest from playwright.sync_api import Page, expect # 测试数据 VALID_USERNAME testuser VALID_PASSWORD TestPass123 INVALID_PASSWORD WrongPass pytest.fixture(scopefunction) def login_page(page: Page): Fixture导航到登录页面 page.goto(https://demo-blog.com/login) yield page def test_successful_login(login_page: Page): 测试用例1成功登录 # 1. 定位元素并操作 login_page.locator(input[nameusername]).fill(VALID_USERNAME) login_page.locator(input[namepassword]).fill(VALID_PASSWORD) login_page.locator(button[typesubmit]).click() # 2. 断言验证是否成功跳转到仪表盘 expect(login_page).to_have_url(**/dashboard) # 断言页面中包含欢迎用户的文本 expect(login_page.locator(.welcome-message)).to_contain_text(VALID_USERNAME) def test_failed_login_with_wrong_password(login_page: Page): 测试用例2密码错误登录失败 login_page.locator(input[nameusername]).fill(VALID_USERNAME) login_page.locator(input[namepassword]).fill(INVALID_PASSWORD) login_page.locator(button[typesubmit]).click() # 断言错误提示信息应该出现 error_message login_page.locator(.alert-error) expect(error_message).to_be_visible() expect(error_message).to_contain_text(密码错误) # 断言页面仍然停留在登录页 expect(login_page).to_have_url(**/login) def test_remember_me_functionality(login_page: Page): 测试用例3记住我功能 login_page.locator(input[nameusername]).fill(VALID_USERNAME) login_page.locator(input[namepassword]).fill(VALID_PASSWORD) login_page.locator(input[nameremember]).check() # 勾选复选框 login_page.locator(button[typesubmit]).click() # 登录成功后关闭浏览器上下文再重新打开检查是否自动登录 # 注意这需要应用实际实现了“记住我”功能且我们能在同一Context中保持状态。 # 更真实的测试可能需要操作Cookie或LocalStorage这里简化演示。 expect(login_page).to_have_url(**/dashboard) # 在实际项目中这里可能会清理Cookie然后重新访问首页检查是否自动跳转到仪表盘。 def test_forgot_password_link(login_page: Page): 测试用例4忘记密码链接 with login_page.context.expect_page() as new_page_info: login_page.locator(text忘记密码).click() # 点击链接预期会打开新标签页 new_page new_page_info.value # 断言新页面应该是忘记密码页面 expect(new_page).to_have_url(**/forgot-password) expect(new_page.locator(h1)).to_have_text(重置密码) new_page.close()运行测试pytest test_login.py -v6.3 测试最佳实践与高级技巧1. 使用Page Object Model (POM) 设计模式当测试套件变大时将页面元素定位和操作封装成类能极大提升代码的可维护性和复用性。# pages/login_page.py class LoginPage: def __init__(self, page: Page): self.page page self.username_input page.locator(input[nameusername]) self.password_input page.locator(input[namepassword]) self.submit_button page.locator(button[typesubmit]) self.error_message page.locator(.alert-error) def navigate(self): self.page.goto(https://demo-blog.com/login) def login(self, username, password): self.username_input.fill(username) self.password_input.fill(password) self.submit_button.click() def get_error_text(self): return self.error_message.text_content()然后在测试中使用def test_login_with_pom(login_page: Page): login LoginPage(login_page) login.navigate() login.login(VALID_USERNAME, INVALID_PASSWORD) expect(login.error_message).to_be_visible()2. 配置与钩子 (Hooks)使用pytest的conftest.py文件进行全局配置比如设置全局超时、截图设置、登录态复用等。# conftest.py import pytest from playwright.sync_api import Page, BrowserContext pytest.fixture(scopesession) def browser_context_args(browser_context_args): 全局浏览器上下文配置 return { **browser_context_args, viewport: {width: 1920, height: 1080}, ignore_https_errors: True, # 忽略HTTPS证书错误测试环境用 } pytest.fixture(scopefunction) def authed_context(context: BrowserContext): 创建一个已登录状态的上下文Fixture page context.new_page() page.goto(https://demo-blog.com/login) # ... 执行登录操作 yield context # 测试结束后清理如果需要 pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): 测试失败时自动截图 outcome yield report outcome.get_result() if report.when call and report.failed: page item.funcargs.get(page) if page: screenshot_path fscreenshots/{item.name}_{report.when}.png page.screenshot(pathscreenshot_path, full_pageTrue) print(f测试失败截图已保存至: {screenshot_path})3. 并行测试Playwright支持在多个浏览器、多个设备上并行运行测试充分利用多核CPU。可以通过pytest-xdist插件实现。# 安装 pip install pytest-xdist # 运行使用3个worker并行 pytest test_login.py -n 34. 视觉回归测试Playwright可以轻松集成像pixelmatch这样的库进行截图对比检查UI是否有意外变更。from PIL import Image import pixelmatch def test_homepage_visual(login_page: Page): login_page.goto(https://demo-blog.com) login_page.screenshot(pathscreenshot.png) # 将 screenshot.png 与基准图片 baseline.png 进行像素对比 # ... 对比逻辑 ...7. 部署、调试与性能优化7.1 在CI/CD中运行Playwright在无GUI的服务器如GitHub Actions, Jenkins, GitLab CI上运行Playwright需要安装其系统依赖。Linux (Ubuntu/Debian):# 使用Playwright CLI安装依赖和浏览器 npx playwright install-deps # 如果使用Node.js的playwright # 对于Python通常只需安装playwright库并执行playwright install但可能需要手动安装一些系统库 sudo apt-get install -y libwoff1 libopus0 libwebpdemux2 libenchant-2-2 libgudev-1.0-0 libsecret-1-0 libhyphen0 libgles2 libevent-2.1-7GitHub Actions 示例配置:name: Playwright Tests on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.10 - name: Install dependencies run: | pip install -r requirements.txt playwright install playwright install-deps # 安装系统依赖 - name: Run tests run: pytest - name: Upload screenshots on failure if: failure() uses: actions/upload-artifactv3 with: name: playwright-screenshots path: screenshots/7.2 调试技巧慢动作模式启动浏览器时设置slow_mo参数让每个操作延迟执行方便观察。browser p.chromium.launch(headlessFalse, slow_mo1000) # 每个操作延迟1秒录制器使用Playwright CLI的codegen命令通过交互自动生成脚本。这是学习API和快速创建原型的神器。playwright codegen https://demo-blog.com浏览器开发者工具在非无头模式下运行直接按F12打开开发者工具查看Console、Network和Elements面板。Playwright Inspector设置PWDEBUG1环境变量运行脚本会自动打开Playwright Inspector可以单步调试、查看选择器、录制动作。PWDEBUG1 python your_script.pyTrace Viewer在测试运行时记录追踪信息生成一个可视化的时间线精确查看每一步发生了什么包括网络请求、DOM快照等。对于调试偶发性的失败测试极其有用。# 在测试开始前启动追踪 context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) # ... 执行测试 ... # 测试结束后保存追踪文件无论成功失败 context.tracing.stop(path trace.zip)使用playwright show-trace trace.zip命令查看。7.3 性能优化与资源管理复用Browser和Context创建Browser和Context是昂贵的操作。在测试套件中使用session或module级别的Fixture来复用它们。并行化如前所述使用pytest-xdist进行测试并行化。选择性安装浏览器如果只测试Chrome就只安装Chromiumplaywright install chromium。合理设置超时为不同的操作设置合适的超时时间。全局超时可以在启动Browser或创建Context时设置也可以在单个操作上覆盖。browser p.chromium.launch(timeout30000) # 全局30秒超时 page.locator(#slow-element).click(timeout15000) # 该操作15秒超时及时清理确保在测试结束后关闭Page和Browser释放资源。使用with语句或pytest的Fixture可以很好地管理生命周期。从环境搭建到核心概念从基础交互到高级网络控制再通过两个完整的实战项目爬虫与测试串联所有知识点最后探讨部署调试与性能优化这条学习路径旨在让你不仅能写出Playwright脚本更能理解其设计哲学从而灵活高效地解决实际工程问题。记住自动化工具的价值在于将人从重复劳动中解放出来而Playwright正是目前实现这一目标最得力的武器之一。