行业资讯
📅 2026/8/9 5:14:48
Python Playwright自动化测试:从原理到实战的完整指南
1. 项目概述为什么说Playwright是Python Web自动化测试的革命者如果你和我一样在Web自动化测试领域摸爬滚打了几年从Selenium WebDriver的“等待元素出现”到处理各种弹窗和iframe再到为不同浏览器维护驱动而头疼那你一定对“稳定”和“高效”这两个词有着近乎偏执的追求。几年前当微软推出Playwright时我最初是抱着怀疑态度的——又一个自动化框架但当我真正把它用在几个大型、复杂的Web应用测试项目后我的看法彻底改变了。Playwright不是Selenium的简单替代品它更像是一个从底层重新思考了“浏览器自动化”这件事的现代解决方案。简单来说Playwright是一个由微软开发的、用于实现端到端E2EWeb测试的自动化库。它支持ChromiumChrome、Edge、Firefox和WebKitSafari三大浏览器引擎并且为Node.js、Python、Java和C#都提供了原生API。我们今天聚焦在Python上。它的“革命性”体现在哪里我总结为三点原生多浏览器支持、无头模式的稳定性、以及基于上下文的强大隔离能力。这听起来可能有点抽象我举个例子以前用Selenium做多浏览器并行测试你得为每个浏览器实例单独管理驱动和会话进程间通信和资源竞争问题常常让你在深夜调试。而Playwright通过其“浏览器上下文”Browser Context的概念让你能在一个浏览器实例内轻松创建多个完全隔离的、带有独立cookie、localStorage和会话的“虚拟浏览器环境”并行执行测试用例资源消耗大幅降低稳定性却显著提升。对于测试工程师、开发自测人员甚至是需要做网页数据抓取和监控的开发者来说Playwright提供了一套更现代、更可靠的工具链。它解决了传统自动化工具的几个核心痛点跨浏览器一致性差、执行速度慢特别是无头模式、以及对现代Web技术如Shadow DOM、网络拦截支持不足的问题。接下来我会带你从零开始深入Playwright在Python下的世界不仅告诉你“怎么用”更会分享我在实际项目中“为什么这么用”以及“踩过哪些坑”。2. 核心设计理念与架构优势解析2.1 与Selenium的根本性差异协议与架构要理解Playwright的强大首先要明白它和前辈Selenium的核心区别。Selenium WebDriver基于W3C WebDriver协议这是一个标准的HTTP REST API。你的测试脚本客户端通过发送HTTP请求如POST /session/{sessionId}/element给浏览器驱动如ChromeDriver驱动再通过调试协议如Chrome DevTools Protocol控制浏览器。这个链条长且通信开销大。Playwright则走了另一条路。它直接使用各个浏览器厂商提供的底层调试协议如CDP for Chromium。更重要的是Playwright启动时会同时启动一个浏览器服务端和一个客户端。你的Python脚本客户端通过WebSocket与Playwright的服务端通信服务端再通过CDP等协议控制浏览器。这种架构带来了几个立竿见影的好处更快的执行速度WebSocket是全双工、长连接比HTTP的请求-响应模式更快减少了建立连接的开销。更丰富的控制能力因为直连调试协议Playwright可以做到许多WebDriver协议不支持的事情比如网络拦截与模拟轻松模拟离线、慢速网络拦截和修改任何网络请求XHR, Fetch。原生输入模拟提供更真实的鼠标移动、键盘输入和触摸事件而不是简单的DOM事件触发。访问浏览器上下文直接操作多个页面Page、弹出窗口Popup、iframe甚至Service Worker。2.2 核心概念Browser, Context 和 Page这是Playwright架构中最精妙的部分理解它们的关系至关重要。Browser代表一个实际的浏览器进程实例。你可以通过playwright.chromium.launch()启动一个Chrome/Edge或者playwright.firefox.launch()启动Firefox。启动浏览器是资源消耗最大的操作。BrowserContext上下文这是Playwright的“王牌”特性。一个Browser实例下可以创建多个完全隔离的BrowserContext。每个Context都拥有独立的Cookie、localStorage、sessionStorage缓存、权限设置如地理位置、通知代理配置为什么这很重要想象一下你需要测试同一个用户在不同标签页登录两个不同账号的场景。在Selenium里这几乎不可能因为cookie共享。在Playwright里你只需要创建两个Context分别登录即可它们互不干扰。这为并行测试和数据隔离提供了完美基础。Page代表一个浏览器标签页或一个弹出窗口。它存在于某个BrowserContext中。我们绝大部分的自动化操作如page.goto(),page.click()都是在Page对象上进行的。它们的关系可以这样类比Browser是电脑Context是电脑上的不同用户账户Page是每个用户账户下打开的浏览器窗口或标签页。这种层级设计让资源管理和测试隔离变得异常清晰和高效。2.3 同步与异步API如何选择Playwright的Python API提供了两套playwright.sync_api同步和playwright.async_api异步。这不是简单的语法糖而是性能的关键。同步API使用with sync_playwright() as p:上下文管理器。代码写起来是线性的更符合传统脚本的思维易于理解和调试。但它本质上是阻塞的。当执行page.click()等待元素时整个线程都在等待。异步API使用async with async_playwright() as p:并且所有方法前都需要加await。它基于Python的asyncio库。它的优势在于可以高效处理I/O等待。当某个页面在等待网络请求或元素加载时事件循环可以去执行其他页面的操作。如何选择新手或简单脚本从同步API开始逻辑直观。高性能爬虫或需要同时控制大量页面的测试务必使用异步API。在我的一个监控项目中需要同时打开50个页面检查状态。使用同步API耗时约120秒而改用异步API后时间缩短到15秒以内因为所有页面的网络等待时间被重叠利用了。与异步Web框架如FastAPI集成自然选择异步API。一个常见的误区是认为异步编程复杂。对于Playwright来说你只需要记住给方法和with语句前加上async/await其编程模型和同步API几乎一致学习成本很低但收益巨大。3. 环境搭建与快速上手实战3.1 一步到位的安装与配置Playwright的安装可能是所有自动化工具里最省心的。它采用“电池包含”理念浏览器驱动和浏览器本身都帮你管理好了。# 1. 安装Playwright Python库 pip install playwright # 2. 安装所需的浏览器二进制文件Chromium, Firefox, WebKit playwright installplaywright install这个命令是关键。它会自动下载Chromium、Firefox和WebKitSafari内核的预备版本到你的用户目录下Windows在%USERPROFILE%\AppData\Local\ms-playwright。这意味着你不需要再去单独下载ChromeDriver或geckodriver也无需手动配置PATH。所有浏览器版本都由Playwright团队测试并锁定保证了API的兼容性和稳定性。如果你只需要特定浏览器可以指定安装playwright install chromium # 只安装ChromiumChrome/Edge内核 playwright install firefox # 只安装Firefox playwright install webkit # 只安装WebKit注意事项与避坑指南网络问题首次安装浏览器可能需要从国外CDN下载几百MB的文件。如果遇到网络超时可以尝试设置环境变量PLAYWRIGHT_DOWNLOAD_HOST为国内镜像源如果有或者使用代理。但请注意必须通过系统或网络层面的合法方式进行Playwright安装命令本身不提供代理参数。系统权限在Linux或macOS上可能需要sudo权限来安装一些系统依赖如libenchant用于字体。根据终端提示操作即可。IDE配置我强烈推荐使用VS Code并安装官方的“Playwright Test for VSCode”扩展。它能提供代码自动补全、测试运行和调试功能体验极佳。3.2 你的第一个自动化脚本从录用到编码Playwright提供了一个强大的工具——Codegen代码生成器。对于初学者或快速探索一个网站的操作流来说这是神器。打开命令行运行playwright codegen https://www.baidu.com这会自动打开一个浏览器窗口和一个“Playwright Inspector”侧边栏。你在浏览器里的所有点击、输入操作都会实时生成对应的Python代码默认是同步API。你可以直接复制这些代码到你的脚本中使用。但作为一名有经验的开发者我们不能只停留在“录制”。让我们手写一个完整的、带错误处理的搜索例子from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError def baidu_search(keyword: str): 一个健壮的百度搜索示例 with sync_playwright() as p: # 启动浏览器headlessFalse表示显示界面方便调试 browser p.chromium.launch(headlessFalse, slow_mo1000) # slow_mo让操作变慢方便观察 # 创建一个浏览器上下文Context context browser.new_context( viewport{width: 1920, height: 1080}, # 设置视口大小 localezh-CN # 设置语言环境为中国 ) # 在上下文中创建一个新页面 page context.new_page() try: # 导航到百度 page.goto(https://www.baidu.com) # 等待页面加载完成直到搜索框出现 page.wait_for_selector(input#kw, statevisible) # 输入搜索关键词 page.fill(input#kw, keyword) # 点击“百度一下”按钮 # 这里使用了更精确的定位通过CSS选择器 文本内容 page.click(input[typesubmit][value百度一下]) # 等待搜索结果页面加载这里等待第一个搜索结果标题出现 page.wait_for_selector(div.result h3 a, stateattached, timeout10000) # 获取第一个搜索结果的标题和链接 first_result page.query_selector(div.result h3 a) if first_result: title first_result.text_content() href first_result.get_attribute(href) print(f第一个搜索结果: {title}) print(f链接: {href}) # 可以点击进入第一个结果 # with page.expect_navigation(): # 等待新页面导航完成 # first_result.click() else: print(未找到搜索结果。) except PlaywrightTimeoutError: print(操作超时可能页面元素未加载或选择器不正确。) # 可以在这里截图方便事后分析 page.screenshot(pathtimeout_error.png) except Exception as e: print(f发生未知错误: {e}) finally: # 无论如何最后都要关闭浏览器释放资源 context.close() browser.close() if __name__ __main__: baidu_search(Playwright Python自动化测试)实操心得wait_for_selector是关键不要假设页面瞬间加载完。在关键操作如点击、输入前使用wait_for_selector等待目标元素变为可用状态visible,attached,hidden等。这是编写稳定脚本的第一原则。善用slow_mo在调试阶段将launch参数中的slow_mo设置为几百毫秒可以让你看清每一步自动化操作非常有用。选择器策略优先使用id、># 多种定位方式示例 page.locator(button).click() # 匹配第一个button元素 page.locator(text登录).click() # 通过文本内容定位 page.locator(#username).fill(admin) # 通过ID定位 page.locator(.submit-btn).click() # 通过类名定位 page.locator(input[nameemail]).fill(testexample.com) # 通过属性定位 page.locator(div:has-text(Welcome)).click() # 使用:has-text()伪类 # 组合定位与链式调用 page.locator(nav).locator(a).filter(has_textHome).click() # 获取多个元素 all_links page.locator(a).all() for link in all_links: print(link.text_content()) # 等待元素状态变化 page.locator(#loading-spinner).wait_for(statehidden)为什么Locator比直接使用page.click(selector)更好Locator对象是惰性求值的并且支持链式调用和复用。更重要的是它内部包含了自动等待机制。当你调用locator.click()时Playwright会自动等待该元素变为可点击状态可见、未禁用、在视口内然后再执行点击。这大大减少了需要手动编写等待语句的情况。4. 高级特性与实战应用场景4.1 网络拦截与模拟测试边缘情况的利器现代Web应用高度依赖API。Playwright允许你拦截和修改任何网络请求这对于测试以下场景不可或缺模拟API失败测试前端在API返回404或500错误时的降级处理。Mock数据在后端接口未准备好时前端可以先进行自动化测试。性能测试模拟慢速网络3G测试页面加载性能。阻止不必要的资源加载如图片、广告加速测试执行。async def intercept_requests(): async with async_playwright() as p: browser await p.chromium.launch() context await browser.new_context() page await context.new_page() # 路由拦截所有请求 await page.route(**/*, lambda route: handle_route(route)) await page.goto(https://example.com) await browser.close() async def handle_route(route): request route.request # 1. 拦截特定API请求并返回Mock数据 if api/user in request.url: await route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({name: Mock User, id: 123}) ) # 2. 阻止图片加载加速测试 elif request.resource_type image: await route.abort() # 3. 修改请求头 elif special-header in request.url: headers request.headers headers[x-custom-token] my-token await route.continue_(headersheaders) # 4. 其他请求正常继续 else: await route.continue_()4.2 处理复杂页面结构iframe、弹窗与新窗口处理iframe和弹窗是传统自动化测试的噩梦。Playwright让这一切变得简单。# 处理iframe page.frame_locator(iframe[namecontent]).locator(button.submit).click() # 处理弹窗alert, confirm, prompt page.on(dialog, lambda dialog: dialog.accept()) # 自动接受所有弹窗 # 或者更精细的控制 def handle_dialog(dialog): if dialog.type alert: print(dialog.message) dialog.accept() elif dialog.type confirm: dialog.dismiss() # 点击取消 page.on(dialog, handle_dialog) # 处理新窗口/标签页 # 方法1监听‘popup’事件由target_blank的链接触发 async with page.expect_popup() as popup_info: page.click(a[target_blank]) new_page await popup_info.value await new_page.wait_for_load_state() print(await new_page.title()) # 方法2获取所有打开的页面 all_pages context.pages second_page all_pages[1]4.3 文件上传与下载Playwright处理文件上传不再需要找隐藏的input typefile元素然后send_keys它提供了更符合用户直觉的方式。# 文件上传 - 推荐方式直接设置文件输入 page.locator(input[typefile]).set_input_files(/path/to/myfile.pdf) # 上传多个文件 page.locator(input[typefile]).set_input_files([file1.pdf, file2.jpg]) # 清空已选文件 page.locator(input[typefile]).set_input_files([]) # 文件下载 # 首先需要在创建context时启用下载 context await browser.new_context(accept_downloadsTrue) page await context.new_page() # 监听下载事件 async with page.expect_download() as download_info: page.click(a#download-link) # 触发下载的链接 download await download_info.value # 等待下载完成并保存到指定路径 save_path f./downloads/{download.suggested_filename} await download.save_as(save_path) print(f文件已下载到: {save_path})4.4 设备模拟与移动端测试Playwright内置了数十种主流移动设备如iPhone, iPad, Pixel的配置可以一键模拟其视口、User-Agent、设备比例等非常适合响应式测试。from playwright.sync_api import sync_playwright def test_mobile_view(): with sync_playwright() as p: # 方法1使用内置设备模拟 iphone p.devices[iPhone 12 Pro] browser p.chromium.launch(headlessFalse) # 创建上下文时传入设备参数 context browser.new_context(**iphone) page context.new_page() page.goto(https://m.example.com) # 此时页面看到的就是iPhone 12 Pro的模拟效果 # 方法2手动指定视口和User-Agent context2 browser.new_context( viewport{width: 390, height: 844}, user_agentMozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) ..., device_scale_factor3, # 视网膜屏缩放 is_mobileTrue, has_touchTrue # 启用触摸事件 ) page2 context2.new_page() # 模拟触摸滑动 page2.touchscreen.tap(100, 200)5. 集成到专业测试框架Pytest实战单独使用Playwright脚本可以做自动化但要构建可维护、可报告、可并行的测试套件必须集成到专业的测试框架中。Pytest是Python生态的不二之选。5.1 基础集成与Fixture使用Pytest的Fixture机制非常适合管理Playwright昂贵的资源如Browser实例。# conftest.py - 放在项目根目录或测试目录下 import pytest from playwright.sync_api import Page, BrowserContext, BrowserType import playwright.sync_api as p pytest.fixture(scopesession) # 整个测试会话只启动一次浏览器 def browser(): # 启动浏览器可以在这里配置全局参数如headless模式 with p.sync_playwright() as playwright: browser playwright.chromium.launch(headlessTrue) # CI环境通常用无头模式 yield browser browser.close() pytest.fixture(scopefunction) # 每个测试函数一个干净的上下文 def context(browser): context browser.new_context( viewport{width: 1920, height: 1080}, localezh-CN, # 可以在这里注入初始状态如登录cookie # storage_stateauth.json ) yield context context.close() pytest.fixture(scopefunction) def page(context): page context.new_page() yield page page.close() # test_example.py def test_baidu_search(page: Page): page.goto(https://www.baidu.com) assert 百度 in page.title() page.fill(#kw, Playwright) page.click(#su) # 使用Playwright内置的断言更强大 expect(page).to_have_title(re.compile(r.*Playwright.*))5.2 使用Pytest插件实现高级功能社区有优秀的pytest-playwright插件它封装了上述Fixture并提供了更多便利功能。pip install pytest-playwright# 使用插件提供的Fixture它自动管理了browser, context, page def test_with_plugin(page): page.goto(https://example.com) # ... # 插件还提供了is_chromium, is_firefox, is_webkit等Fixture用于编写特定浏览器的测试 def test_only_chrome(page, is_chromium): if not is_chromium: pytest.skip(此测试仅适用于Chromium浏览器) # ... Chrome-specific test5.3 生成漂亮的测试报告集成AllureAllure报告能直观展示测试步骤、截图和错误信息是团队协作的利器。pip install allure-pytest# conftest.py 中配置自动截图 pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() # 仅当测试失败时执行 if report.when call and report.failed: # 假设page Fixture在测试中可用 if page in item.fixturenames: page item.funcargs[page] # 将截图附加到Allure报告 allure.attach( page.screenshot(full_pageTrue, typepng), namefscreenshot_{item.name}, attachment_typeallure.attachment_type.PNG ) # 也可以附加页面源代码 allure.attach( page.content(), namefpage_source_{item.name}, attachment_typeallure.attachment_type.HTML ) # 运行测试并生成报告 # pytest --alluredir./allure-results ./tests # allure serve ./allure-results # 本地查看报告6. 常见问题排查与性能优化实录6.1 元素定位失败稳定性提升技巧这是自动化测试中最常见的问题。除了基本的等待还有以下高级技巧使用page.wait_for_function等待复杂条件# 等待直到某个元素包含特定文本 await page.wait_for_function( () { const el document.querySelector(.status); return el el.textContent.includes(加载完成); } )自定义重试逻辑from tenacity import retry, stop_after_attempt, wait_exponential retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) def click_with_retry(page, selector): 带有指数退避重试的点击函数 page.click(selector) # 使用 click_with_retry(page, button.submit)应对动态ID和类名使用XPath的contains函数或CSS属性选择器。# 不好的选择器id是动态生成的 # page.click(#button-123456) # 好的选择器使用稳定的属性部分 page.click([id^button-]) # id以button-开头 page.click([data-testidsubmit-btn]) # 最好让开发加上固定的data-testid page.click(button:has-text(提交)) # 结合文本6.2 异步操作与竞态条件在异步API中如果不注意操作顺序很容易产生竞态条件。# 错误示例点击后立即断言此时导航可能还未开始 await page.click(a#next-page) await expect(page).to_have_url(/page2) # 可能失败 # 正确示例使用 page.wait_for_navigation 或 page.expect_navigation async with page.expect_navigation(): await page.click(a#next-page) # 此时导航已完成可以安全断言 await expect(page).to_have_url(/page2) # 对于非导航的异步操作如API调用后的UI更新使用等待选择器 await page.click(button#fetch-data) await page.wait_for_selector(.data-loaded, statevisible)6.3 性能优化让测试跑得更快复用Browser实例如前所述启动Browser开销大。使用Pytest的session作用域Fixture让所有测试共享一个Browser实例但各自使用独立的Context。并行执行Pytest可以通过pytest-xdist插件实现并行。pip install pytest-xdist pytest -n auto ./tests # 自动根据CPU核心数并行确保你的测试用例之间是独立的通过独立的Context隔离这是并行成功的前提。禁用不必要的功能在CI环境中可以关闭一些功能加速。browser playwright.chromium.launch( headlessTrue, args[ --disable-gpu, --disable-dev-shm-usage, # 解决Docker中共享内存问题 --disable-setuid-sandbox, --no-sandbox ] ) context browser.new_context( java_script_enabledTrue, # 默认True如果测试静态页可设为False ignore_https_errorsTrue, # 忽略HTTPS证书错误测试环境 bypass_cspTrue # 绕过内容安全策略谨慎使用 )智能等待避免sleep绝对不要使用time.sleep(10)。使用Playwright提供的条件等待wait_for_selector,wait_for_function,expect。6.4 在CI/CD流水线中运行在GitHub Actions、GitLab CI或Jenkins中运行Playwright测试需要注意环境配置。# .github/workflows/playwright.yml 示例 (GitHub 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 --with-deps chromium # 只安装Chromium及其系统依赖 - name: Run tests run: | pytest --browser chromium --headless ./tests - name: Upload test results (if failed) if: failure() uses: actions/upload-artifactv3 with: name: playwright-screenshots path: ./test-results/ # 你的测试截图输出目录关键点在CI中使用--headless模式并使用playwright install --with-deps确保安装所有必要的系统库如字体库。对于Linux Docker镜像可能需要额外安装一些依赖。7. 超越测试Playwright的其他应用场景Playwright的能力不止于测试。它的稳定性和强大的浏览器控制能力使其在其他领域也大放异彩。7.1 网页截图与PDF生成生成网页快照或PDF用于监控、归档或报告。async def generate_assets(url): async with async_playwright() as p: browser await p.chromium.launch() context await browser.new_context(viewport{width: 1280, height: 800}) page await context.new_page() await page.goto(url) await page.wait_for_load_state(networkidle) # 等待网络空闲 # 1. 截图可视区域 await page.screenshot(pathscreenshot.png) # 2. 全屏截图 await page.screenshot(pathfullpage.png, full_pageTrue) # 3. 对特定元素截图 element page.locator(.chart) await element.screenshot(pathchart.png) # 4. 生成PDF支持页眉页脚 await page.pdf( pathreport.pdf, formatA4, print_backgroundTrue, display_header_footerTrue, header_templatediv stylefont-size:10px; margin-left:20px;Page span classpageNumber/span of span classtotalPages/span/div, margin{top: 1cm, bottom: 1cm} ) await browser.close()7.2 端到端监控与健康检查编写脚本定期检查关键业务流程是否畅通。import asyncio import time from playwright.async_api import async_playwright, TimeoutError SITES_TO_CHECK [ {name: 首页, url: https://myapp.com, check_selector: .hero-section}, {name: 登录页, url: https://myapp.com/login, check_selector: form#login}, {name: API状态, url: https://api.myapp.com/health, check_text: {status:ok}}, ] async def check_site(site): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) context await browser.new_context() page await context.new_page() try: # 设置全局超时 page.set_default_timeout(30000) response await page.goto(site[url], wait_untilnetworkidle) if not response or not response.ok: return {site: site[name], status: ERROR, detail: fHTTP {response.status if response else No Response}} if check_selector in site: await page.wait_for_selector(site[check_selector], timeout10000) status OK elif check_text in site: content await page.text_content(body) if site[check_text] in content: status OK else: status ERROR detail Expected text not found else: status OK return {site: site[name], status: status, detail: Check passed} except TimeoutError: return {site: site[name], status: TIMEOUT, detail: Page load or element check timeout} except Exception as e: return {site: site[name], status: ERROR, detail: str(e)} finally: await browser.close() async def main(): tasks [check_site(site) for site in SITES_TO_CHECK] results await asyncio.gather(*tasks, return_exceptionsTrue) for result in results: print(f{result[site]}: {result[status]} - {result.get(detail, )}) if result[status] ! OK: # 发送告警邮件、Slack、钉钉等 send_alert(result) # 用 cron 或 Celery 定时执行这个脚本7.3 自动化数据抓取需谨慎合法使用对于需要JavaScript渲染的动态网页Playwright是比RequestsBeautifulSoup更强大的工具。async def scrape_dynamic_content(): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) context await browser.new_context( user_agentMozilla/5.0 ... # 伪装成普通浏览器 ) page await context.new_page() await page.goto(https://example.com/data-table) # 等待表格数据通过JS加载 await page.wait_for_selector(table tbody tr) # 提取数据 rows await page.locator(table tbody tr).all() data [] for row in rows: cells await row.locator(td).all_text_contents() data.append({ name: cells[0], value: cells[1], date: cells[2] }) # 处理分页 while await page.locator(button.next-page:not([disabled])).is_visible(): await page.click(button.next-page) await page.wait_for_load_state(networkidle) # ... 继续提取新页面的数据 await browser.close() return data重要提醒用于数据抓取时务必遵守网站的robots.txt协议尊重版权控制请求频率避免对目标服务器造成压力。商业用途需获得授权。从我的实际经验来看Playwright最大的价值在于它统一了开发、测试和运维的浏览器自动化需求。开发者可以用它做E2E测试测试工程师用它构建稳定的自动化套件运维可以用它做监控。它学习曲线平缓但天花板很高。当你熟悉了它的核心概念Browser, Context, Page和异步模式后你会发现很多以前棘手的Web自动化问题现在都有了优雅的解决方案。它可能不是所有场景下的银弹但对于现代Web应用的自动化而言它无疑是当前最强大、最值得投入学习的工具之一。