1. 项目概述:为什么是Python+Playwright?

如果你正在寻找一个既能做Web自动化测试,又能轻松搞定网页数据抓取,甚至还能模拟复杂用户交互的工具链,那么“Python + Playwright”这个组合,很可能就是你一直在找的答案。我接触过不少自动化框架,从早期的Selenium到后来的Puppeteer,再到现在的Playwright,可以说Playwright在易用性、功能强大和稳定性上,给了我相当大的惊喜。它不是一个简单的测试工具,而是一个现代的浏览器自动化库,由微软出品,原生支持Chromium、Firefox和WebKit三大浏览器引擎,这意味着你写的脚本几乎可以在所有主流浏览器上无缝运行。

这个组合的核心价值在于,它极大地降低了自动化操作的门槛。你不需要再为不同浏览器下载不同的驱动,也不用担心复杂的元素定位和异步等待问题。Playwright的API设计非常人性化,配合Python这种语法简洁、生态丰富的语言,即使是编程新手,也能在短时间内上手,实现诸如自动填写表单、批量下载文件、监控网页变化、执行端到端测试等任务。无论是测试工程师、数据分析师,还是运营人员,只要你有重复性的网页操作需求,Python+Playwright都能帮你从枯燥的劳动中解放出来。

2. 环境搭建与核心工具链解析

工欲善其事,必先利其器。开始之前,我们需要把“战场”准备好。这一部分我会详细拆解每一步,并解释为什么这么选,避免你走弯路。

2.1 Python环境:版本选择与虚拟环境管理

首先,你需要一个Python环境。我强烈推荐使用 Python 3.8或更高版本 。Playwright的一些新特性(比如更快的API和更好的异步支持)在3.8+上表现更佳。直接从Python官网下载安装包是最稳妥的方式。

注意:安装时务必勾选“Add Python to PATH”选项,这能避免后续在命令行中找不到Python的尴尬。

安装好Python后,第一件事不是急着装Playwright,而是建立 虚拟环境(Virtual Environment) 。这是Python开发中的最佳实践,它能将每个项目的依赖隔离起来,防止不同项目间的包版本冲突。想象一下,你项目A需要Playwright 1.40,项目B需要1.35,如果没有虚拟环境,你只能二选一,非常麻烦。

创建虚拟环境很简单。打开你的终端(Windows上是CMD或PowerShell,Mac/Linux上是Terminal),进入你的项目目录,然后运行:

# 创建名为 venv 的虚拟环境
python -m venv venv

创建完成后,你需要激活它:

  • Windows (CMD): venv\Scripts\activate
  • Windows (PowerShell): .\venv\Scripts\Activate.ps1 (可能需要先执行 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser 来允许脚本执行)
  • Mac/Linux: source venv/bin/activate

激活后,你的命令行提示符前通常会显示 (venv) ,表示你已经在这个独立的Python环境中了。

2.2 Playwright安装:一行命令与背后的浏览器管理

环境准备好了,现在安装Playwright。在激活的虚拟环境中,执行:

pip install playwright

这条命令会安装Playwright的核心Python库。但光有这个库还不够,Playwright需要对应的浏览器二进制文件来驱动。所以安装完库之后,你需要安装浏览器:

playwright install

这条命令会下载Chromium、Firefox和WebKit的预备版本。这是Playwright设计精妙的地方之一:它管理的是 特定版本的、经过测试的浏览器 ,而不是你系统里安装的Chrome或Edge。这保证了脚本运行环境的一致性,今天在你电脑上能跑,明天在服务器上也能跑,彻底避免了“在我机器上是好的”这类问题。

实操心得: playwright install 默认会安装所有浏览器,如果你确定只用Chromium,可以运行 playwright install chromium 来节省时间和磁盘空间。首次安装因为要下载浏览器,可能会比较慢,请保持网络通畅。

2.3 IDE选择:为什么推荐VS Code及其关键插件

写代码需要一个顺手的编辑器。对于Python+Playwright,我首推 Visual Studio Code (VS Code) 。它免费、轻量、插件生态极其丰富。

安装好VS Code后,有几个必装的插件能极大提升效率:

  1. Python (Microsoft) :提供Python语言支持、智能提示、调试、代码格式化(如自动排序import语句)等功能。
  2. Pylance (Microsoft) :一个高性能的语言服务器,能提供更精准的代码补全和类型检查。
  3. Playwright Test for VSCode (Microsoft) :官方插件,可以直接在VS Code里运行和调试Playwright测试脚本,查看测试报告和追踪(Trace)。

配置Python解释器:在VS Code中,按 Ctrl+Shift+P (Mac: Cmd+Shift+P ),输入“Python: Select Interpreter”,然后选择你刚才创建的虚拟环境下的 python.exe (路径类似 ./venv/Scripts/python.exe )。这样,VS Code就会使用虚拟环境中的包来提供智能提示和运行脚本。

3. 从零到一:你的第一个自动化脚本

理论说再多,不如动手写一行代码。让我们从一个最简单的脚本开始,感受一下Playwright的魔力。

3.1 脚本骨架:同步与异步模式的选择

Playwright支持两种编程模式: 同步 异步 。对于初学者,我建议从同步API开始,因为它更符合我们线性的思维习惯,写起来像传统的脚本。等到需要处理高并发(比如同时打开几十个页面)时,再考虑异步模式。

下面是一个最基础的同步脚本框架,它完成了打开浏览器、访问网页、截图和关闭浏览器这一完整流程:

from playwright.sync_api import sync_playwright

def main():
    # 1. 启动Playwright,它负责管理浏览器进程
    with sync_playwright() as p:
        # 2. 启动一个Chromium浏览器实例,headless=False表示显示浏览器界面
        browser = p.chromium.launch(headless=False)
        # 3. 创建一个新的浏览器上下文(Context),类似于一个独立的隐身会话
        context = browser.new_context()
        # 4. 在上下文中打开一个新页面(Page)
        page = context.new_page()
        
        # 5. 导航到百度首页
        page.goto("https://www.baidu.com")
        
        # 6. 对当前页面进行截图并保存
        page.screenshot(path="baidu_homepage.png")
        
        # 7. 等待3秒,方便我们观察
        page.wait_for_timeout(3000)
        
        # 8. 关闭上下文和浏览器(with语句会在结束时自动关闭,这里显式写出以示流程)
        context.close()
        browser.close()

if __name__ == "__main__":
    main()

将这段代码保存为 first_script.py ,然后在终端里运行 python first_script.py 。你会看到一个Chromium浏览器窗口弹出,打开百度首页,截图保存后关闭。恭喜你,第一个自动化脚本成功了!

3.2 核心对象模型:Browser, Context, Page详解

理解Playwright的三个核心对象至关重要,它们构成了自动化操作的层级关系:

  • Browser :代表一个浏览器进程(如Chromium)。你可以通过它启动浏览器,一个Browser实例可以创建多个Context。
  • Context :浏览器上下文。这相当于一个 独立的会话 ,拥有独立的cookie、localStorage、缓存等。你可以用它来模拟多个用户同时登录,或者实现多账号隔离操作。 browser.new_context() 可以传入很多参数,比如设置视窗大小、用户代理(User-Agent)、忽略HTTPS错误等。
  • Page :页面。这是你主要交互的对象,代表一个标签页。绝大部分操作,如点击、输入、获取文本,都是在Page对象上完成的。

这种设计带来了极大的灵活性。例如,你可以用一个Browser打开两个独立的Context,模拟两个完全隔离的用户环境进行测试。

3.3 基础操作:导航、等待与截图

上面的脚本已经展示了最基础的三个操作:

  1. page.goto(url) :导航到指定URL。这是所有操作的起点。
  2. page.wait_for_timeout(ms) :强制等待指定毫秒数。 这是一个需要谨慎使用的方法 ,因为它是一种“硬等待”,无论页面是否加载完成,它都会死等。在实际项目中,我们应优先使用更智能的等待方式(见下文)。
  3. page.screenshot() :截图。除了全屏,你还可以通过 page.screenshot(path="xx.png", full_page=True) 来截取整个滚动页面的长图,或者通过 page.locator("selector").screenshot() 对特定元素截图。这在记录bug或生成报告时非常有用。

4. 元素定位:与页面交互的核心

自动化脚本的精髓在于“模拟人操作”。人要操作网页,首先要找到按钮、输入框等元素。Playwright提供了多种强大且稳定的元素定位方式。

4.1 定位器(Locator):Playwright的定位哲学

Playwright推荐使用 page.locator(selector) 来创建定位器。定位器代表一个或一组元素,但它不会立即去查找元素,而是定义了一个查找规则。真正的查找操作发生在你调用如 click() , fill() 等方法时。这种“懒加载”模式性能更好。

定位器的核心是选择器(selector)。Playwright支持CSS选择器、XPath以及一些特有的文本定位方式。

4.2 主流定位策略详解与实战

1. CSS选择器:最常用、性能最好 CSS选择器是Web开发的标准,也是Playwright首选的定位方式。它通过标签名、ID、类名、属性等来定位元素。

# 通过ID定位搜索框(百度首页)
search_box = page.locator("#kw")
# 通过类名定位搜索按钮
search_button = page.locator(".s_btn")
# 通过属性定位
a_link = page.locator("a[href='https://news.baidu.com']")

2. XPath:功能强大,但慎用 XPath可以在整个DOM树中进行复杂查询,当CSS选择器无能为力时(比如根据文本内容定位),可以考虑XPath。但XPath通常更脆弱,页面结构微调就可能导致定位失败。

# 定位包含“新闻”文本的链接
news_link = page.locator("//a[contains(text(), '新闻')]")

3. 文本定位:Playwright的特色便捷方式 对于通过文本内容定位,Playwright提供了更简洁的语法:

  • page.locator("text=新闻") :定位精确文本为“新闻”的元素。
  • page.locator("text=百度一下") :同上。
  • page.get_by_text("新闻") :另一种文本定位方式,属于Locator API的扩展,可读性更好。

4. 角色定位(Role):面向可访问性的稳健定位 这是我最推荐在复杂项目中使用的定位方式之一。它根据元素的ARIA角色(如button, link, textbox)来定位,通常比基于样式(CSS类)的定位更稳定,因为ARIA角色与功能相关,不易随UI改版而变化。

# 定位搜索框(通常是一个文本输入框)
search_box = page.get_by_role("textbox", name="搜索")
# 定位搜索按钮
search_button = page.get_by_role("button", name="百度一下")

实操心得:优先使用 get_by_role() , get_by_text() , get_by_label() 这类语义化的定位器。它们使代码更易读,且通常与UI的可用性关联,比依赖具体CSS类名的定位更健壮。在开发者工具中,可以使用“检查”元素,查看其ARIA属性来辅助编写。

4.3 等待策略:告别“硬等待”,拥抱智能等待

前面提到的 wait_for_timeout 是下策。Playwright内置了 自动等待 机制,这才是正确姿势。大多数操作(如 click , fill , goto )在执行前,Playwright会自动检查元素是否满足一系列条件(如可见、可交互、稳定等)。

此外,你还可以使用显式等待:

  • page.wait_for_selector(selector) :等待某个选择器匹配的元素出现在DOM中。
  • page.wait_for_function(js_function) :等待一个JavaScript函数返回真值。
  • locator.wait_for(state="attached|visible|hidden") :等待定位器对应的元素达到特定状态。

例如,在点击搜索按钮后,等待结果页面加载完成:

search_button.click()
# 等待结果页面的某个特定元素(如结果统计)出现
page.wait_for_selector("#content_left")

这种等待方式基于页面状态,比固定等待时间更可靠、更快速。

5. 模拟用户交互:点击、输入与更多

定位到元素后,就可以模拟人的操作了。Playwright的API非常直观。

5.1 基础交互API

# 1. 输入文本:填充输入框、文本框
page.locator("#kw").fill("Playwright自动化教程")
# fill() 会先清空输入框再输入,如果想追加文本,可以用 type()
# page.locator("#kw").type("追加的文字")

# 2. 点击:点击按钮、链接
page.locator(".s_btn").click()
# 支持多种点击选项,如 force=True(强制点击即使元素被遮挡)
# page.locator("button").click(force=True)

# 3. 勾选/取消勾选复选框、单选框
page.locator("#agree-checkbox").check()
page.locator("#newsletter").uncheck()

# 4. 选择下拉框选项
page.locator("#city-select").select_option("beijing") # 通过value选择
page.locator("#city-select").select_option(label="北京") # 通过显示文本选择

# 5. 上传文件
page.locator("input[type='file']").set_input_files(["path/to/file1.pdf", "path/to/file2.jpg"])

# 6. 键盘操作
page.locator("#kw").press("Enter") # 在搜索框按回车
# 组合键
page.keyboard.press("Control+A") # 全选 (Mac上是 Meta+A)

这些API基本覆盖了90%的网页交互场景。每个方法都内置了等待和重试逻辑,大大提升了脚本的稳定性。

5.2 处理弹窗与对话框

网页上的alert、confirm、prompt对话框会阻塞脚本执行。Playwright可以监听并处理它们:

# 在触发弹窗的操作之前,先设置监听器
page.on("dialog", lambda dialog: dialog.accept()) # 自动接受(点击确定)
# 或者
page.on("dialog", lambda dialog: dialog.dismiss()) # 自动取消(点击取消)
# 如果需要获取提示信息或输入内容
def handle_dialog(dialog):
    print(f"对话框信息: {dialog.message}")
    if dialog.type == "prompt":
        dialog.accept("这是输入的内容") # 在prompt中输入文本并接受
    else:
        dialog.accept()
page.on("dialog", handle_dialog)

通过 page.on 注册事件监听器,就能在对话框弹出时自动处理,让脚本流畅运行下去。

5.3 鼠标与键盘高级模拟

对于拖拽、悬停(Hover)等复杂交互,Playwright也能轻松应对:

# 鼠标悬停
page.locator(".menu-item").hover()
# 拖拽元素A到元素B的位置
page.drag_and_drop("#item-a", "#container-b")
# 更精细的鼠标控制
page.mouse.move(x, y) # 移动鼠标到坐标(x, y)
page.mouse.down() # 按下鼠标左键
page.mouse.move(x + 100, y) # 拖动
page.mouse.up() # 松开鼠标左键

键盘操作除了简单的 press ,还可以模拟组合键和输入序列:

page.keyboard.type("Hello World!") # 模拟逐个字符输入
page.keyboard.press("Control+C") # 复制
page.keyboard.press("Control+V") # 粘贴

6. 框架进阶:多页面、上下文与网络拦截

当你的自动化需求变得复杂,比如需要管理多个标签页、模拟不同用户,或者需要监控和修改网络请求时,Playwright更强大的功能就派上用场了。

6.1 管理多个页面与浏览器上下文

一个Browser Context下可以打开多个Page(标签页):

# 打开第一个页面(百度)
page1 = context.new_page()
page1.goto("https://www.baidu.com")
# 打开第二个页面(谷歌)
page2 = context.new_page()
page2.goto("https://www.google.com")
# 在页面间切换操作
page1.bring_to_front() # 将page1切换到前台
page1.locator("#kw").fill("test on baidu")
page2.bring_to_front()
page2.locator("[name='q']").fill("test on google")

创建独立的浏览器上下文,实现完全隔离的会话(如多账号登录):

# 创建两个独立的上下文(像两个不同的隐身窗口)
context1 = browser.new_context()
context2 = browser.new_context()
page_user1 = context1.new_page() # 用户1的页面
page_user2 = context2.new_page() # 用户2的页面
# 它们之间的cookies、本地存储互不干扰

6.2 网络请求的监听与操控

Playwright可以拦截和修改任何网络请求和响应,这对于性能测试、模拟后端数据、屏蔽广告或捕获API数据极其有用。

# 1. 路由(Route):拦截特定请求并返回自定义响应
from playwright.sync_api import Route

def handle_route(route: Route):
    # 拦截所有图片请求,并中止加载以加快速度
    if route.request.resource_type == "image":
        route.abort()
    # 拦截特定API请求,返回模拟数据
    elif "api/user/profile" in route.request.url:
        route.fulfill(
            status=200,
            content_type="application/json",
            body=json.dumps({"name": "Mock User", "age": 30})
        )
    else:
        route.continue_() # 其他请求正常继续

page.route("**/*", handle_route) # **/* 匹配所有请求

# 2. 监听请求和响应
def on_request(request):
    print(f">> {request.method} {request.url}")
def on_response(response):
    if response.status >= 400:
        print(f"<< {response.status} {response.url}")

page.on("request", on_request)
page.on("response", on_response)

这个功能非常强大,你可以用它来:

  • 性能优化 :阻止不必要的资源(如图片、样式表)加载,加快自动化速度。
  • 测试桩(Stub) :在后端服务不可用或不稳定时,用模拟数据保证前端测试的进行。
  • 数据捕获 :监听特定的XHR/Fetch请求,直接获取JSON数据,用于爬虫或监控。

6.3 处理iframe与Shadow DOM

现代网页中,iframe(内嵌框架)和Shadow DOM(影子DOM)很常见。Playwright能无缝地处理它们。

# 处理iframe:先定位到iframe元素,再获取其内部的content frame
iframe_element = page.locator("iframe#my-iframe")
iframe = iframe_element.content_frame()
# 现在可以在iframe内部进行操作了
iframe.locator("button").click()

# 处理Shadow DOM:使用 `::shadow` 或 `/deep/` 选择器穿透(CSS穿透已不推荐,Playwright有更好方式)
# Playwright的定位器可以直接穿透Shadow DOM(如果浏览器支持)
# 例如,对于一个自定义元素 <my-button>,其内部有一个shadow root包含一个button
page.locator("my-button::shadow button").click()
# 更通用的方法是先定位到shadow host,再获取其shadow root(需要评估模式)
shadow_host = page.locator("my-button")
shadow_root = shadow_host.evaluate_handle("el => el.shadowRoot")
inner_button = shadow_root.query_selector("button")
# 但更简单的方式是,如果元素有可访问的名称或角色,直接用 get_by_role
page.get_by_role("button", name="Submit inside shadow").click()

7. 实战项目:构建一个简单的自动化测试/爬虫脚本

让我们把前面学的知识串起来,完成一个稍微复杂点的实战项目: 自动登录一个演示网站,并获取登录后的用户信息 。假设目标网站是 https://example.com/login

7.1 需求分析与脚本设计

目标:

  1. 打开登录页面。
  2. 输入用户名和密码。
  3. 点击登录按钮。
  4. 等待跳转到用户主页。
  5. 提取主页上的欢迎信息(如“Hello, [用户名]”)。
  6. 验证登录成功,并打印提取的信息。
  7. 妥善处理异常(如登录失败、元素未找到)。

我们将采用稳健的定位策略(优先使用角色定位)和智能等待。

7.2 分步实现与代码详解

import time
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError

def auto_login_and_fetch_info():
    with sync_playwright() as p:
        # 启动浏览器,显示界面方便调试
        browser = p.chromium.launch(headless=False, slow_mo=1000) # slow_mo 让动作慢一点,方便观察
        # 创建上下文,可以设置视窗大小和用户代理
        context = browser.new_context(viewport={'width': 1920, 'height': 1080})
        page = context.new_page()
        
        try:
            print("步骤1: 导航到登录页面...")
            page.goto("https://example.com/login")
            # 等待登录表单的关键元素出现,确保页面加载完成
            page.wait_for_selector("form", state="visible")
            
            print("步骤2: 填写登录表单...")
            # 使用角色定位,假设用户名输入框的aria-label是'Username'
            username_input = page.get_by_role("textbox", name="Username")
            username_input.fill("test_user")
            
            # 定位密码框,类型为'password'
            password_input = page.get_by_role("textbox", name="Password", type="password")
            password_input.fill("test_pass123")
            
            print("步骤3: 点击登录按钮...")
            # 定位提交按钮
            login_button = page.get_by_role("button", name="Sign In")
            login_button.click()
            
            print("步骤4: 等待登录成功并跳转...")
            # 等待导航完成,通常登录后会跳转到主页或仪表盘
            # 这里我们等待一个登录后才会出现的元素,比如用户头像或欢迎语
            page.wait_for_url("**/dashboard**") # 等待URL包含/dashboard
            welcome_msg_locator = page.get_by_text("Hello,") # 等待欢迎文本出现
            welcome_msg_locator.wait_for(state="visible")
            
            print("步骤5: 提取用户信息...")
            # 假设欢迎信息格式是 "Hello, John Doe!"
            full_welcome_text = welcome_msg_locator.text_content()
            print(f"提取到的欢迎信息: {full_welcome_text}")
            # 简单提取用户名(根据实际文本格式调整)
            if "," in full_welcome_text:
                username = full_welcome_text.split(",")[1].strip().replace("!", "")
                print(f"解析出的用户名: {username}")
            
            print("步骤6: 验证登录成功...")
            # 检查页面是否包含登出按钮等登录成功标志
            logout_button = page.get_by_role("button", name="Logout")
            if logout_button.is_visible():
                print("✅ 登录成功验证通过!")
            else:
                print("⚠️  未找到登出按钮,登录状态可能异常。")
                
            # 可以继续其他操作,比如截图
            page.screenshot(path="dashboard_after_login.png")
            print("截图已保存。")
            
        except PlaywrightTimeoutError as e:
            print(f"❌ 操作超时,可能页面元素未加载或选择器错误: {e}")
            # 保存当前页面截图和HTML,便于调试
            page.screenshot(path="error_state.png")
            html = page.content()
            with open("error_page.html", "w", encoding="utf-8") as f:
                f.write(html)
            print("已保存错误状态截图和HTML。")
        except Exception as e:
            print(f"❌ 发生未知错误: {e}")
        finally:
            # 等待几秒后关闭,方便人工查看结果
            time.sleep(5)
            print("脚本执行完毕,关闭浏览器。")
            context.close()
            browser.close()

if __name__ == "__main__":
    auto_login_and_fetch_info()

7.3 错误处理与脚本健壮性提升

上面的脚本包含了基本的 try...except 错误处理。在实际项目中,还需要考虑更多:

  1. 更精细的等待与重试 :对于不稳定的网络或动态加载的内容,可以使用 locator.wait_for() 并设置超时和重试间隔。

    # 等待元素出现,最多等10秒,每500毫秒检查一次
    element = page.locator(".dynamic-content")
    element.wait_for(state="visible", timeout=10000)
    
  2. 条件判断 :在操作前判断元素状态。

    if page.locator("#submit-btn").is_enabled():
        page.locator("#submit-btn").click()
    else:
        print("按钮不可点击,可能数据未填写完整。")
    
  3. 日志记录 :使用Python的 logging 模块替代 print ,可以输出不同级别(INFO, ERROR)的日志到文件,方便后续排查。

  4. 配置化 :将URL、用户名、密码、选择器等写入配置文件(如 config.yaml .env 文件),使脚本更易于维护和在不同环境运行。

  5. 使用Playwright的追踪(Trace)功能 :当脚本失败时,可以记录详细的追踪信息,包括每一步的操作截图、网络请求和DOM快照。

    # 在context创建时启用追踪
    context = browser.new_context()
    context.tracing.start(screenshots=True, snapshots=True, sources=True)
    # ... 执行脚本 ...
    # 脚本失败时保存追踪文件
    context.tracing.stop(path = "trace.zip")
    

    这个 trace.zip 可以用Playwright的命令行工具 playwright show-trace trace.zip 打开,像看视频一样回放脚本执行过程,是调试复杂问题的利器。

8. 常见问题排查与性能优化技巧

即使按照最佳实践编写脚本,也难免会遇到问题。这里汇总了一些我踩过的坑和解决方案。

8.1 元素定位失败:原因分析与解决

这是最常见的问题。脚本报错 TimeoutError: Timeout 30000ms exceeded ,通常意味着定位器没找到元素。

排查步骤:

  1. 确认页面加载完成 :在操作前加一个 page.wait_for_load_state("networkidle") 或等待某个稳定出现的元素。
  2. 验证选择器 :在浏览器开发者工具的Console中,用 $$("你的CSS选择器") $x("你的XPath") 测试你的选择器是否能选中元素。
  3. 检查iframe/Shadow DOM :目标元素是否在iframe或Shadow DOM内部?如果是,需要先切换到对应的frame或穿透Shadow DOM。
  4. 检查动态内容 :元素是否是JavaScript动态生成的?可能需要等待特定事件或使用 page.wait_for_function
  5. 使用更稳健的定位器
    • 优先用 get_by_role() , get_by_text() , get_by_label()
    • 避免使用绝对XPath或依赖具体样式(如 .class-name:nth-child(3) )的CSS选择器。
    • 可以组合使用: page.locator("div").filter(has_text="Submit")

8.2 脚本运行慢:性能优化实战

自动化脚本速度很重要。以下方法可以显著提升执行效率:

  1. 启用无头模式(Headless) :这是最大的性能提升点。在不需要观察浏览器界面时,设置 headless=True (默认就是True)。

    browser = p.chromium.launch(headless=True) # 无界面运行,速度更快
    
  2. 拦截不必要的资源 :如前所述,通过路由(Route)阻止图片、样式表、字体等资源的加载。

    def route_handler(route):
        if route.request.resource_type in ["image", "stylesheet", "font"]:
            route.abort()
        else:
            route.continue_()
    page.route("**/*", route_handler)
    

    注意:如果测试需要验证UI样式,则不能拦截样式表。

  3. 重用浏览器上下文 :对于多个测试场景,不要为每个场景都启动关闭浏览器。可以启动一个浏览器,为每个测试用例创建独立的上下文,这样能节省大量启动时间。

  4. 并行执行 :Playwright原生支持并行测试。可以利用 playwright-pytest 插件,或者使用Python的 concurrent.futures 模块来并发运行多个浏览器上下文或页面。

  5. 避免不必要的等待 :用智能等待( wait_for_selector , wait_for_url )替代固定的 time.sleep page.wait_for_timeout

8.3 环境与依赖问题

  1. Playwright安装浏览器慢/失败

    • 设置环境变量 PLAYWRIGHT_DOWNLOAD_HOST 为国内镜像源,例如 https://npmmirror.com/mirrors/playwright/
    • 使用 playwright install --with-deps chromium 只安装Chromium及其系统依赖。
    • 检查网络连接和代理设置。
  2. 脚本在CI/CD(如Jenkins, GitHub Actions)上运行失败

    • CI环境通常是无图形界面的服务器。确保已安装所有必要的系统依赖。Playwright提供了命令来安装: playwright install-deps
    • 确保在无头模式下运行 ( headless=True )。
    • 如果CI环境内存较小,考虑禁用沙箱以减少资源占用(不推荐用于不受信任的网站): browser = p.chromium.launch(args=['--no-sandbox'])
  3. 与Selenium等其他工具共存冲突 :如果系统同时安装了Selenium的WebDriver,一般不会有冲突,因为Playwright使用自己管理的浏览器。但要注意端口占用问题(Playwright会启动一个后台服务)。

8.4 调试技巧:让问题无处遁形

  1. 慢动作与录制 :启动浏览器时设置 slow_mo=毫秒数 ,让每个操作都放慢,方便观察。 playwright codegen 命令可以打开一个浏览器并录制你的操作生成脚本,是学习定位器的好工具。

  2. 截图和HTML转储 :在关键步骤或失败时截图、保存页面HTML,如上文错误处理所示。

  3. 开启详细日志

    import os
    os.environ['PWDEBUG'] = '1' # 打开Playwright的调试模式,会显示操作日志
    # 或者
    browser = p.chromium.launch(headless=False, devtools=True) # 启动时打开开发者工具
    
  4. 使用Playwright Inspector :设置环境变量 PWDEBUG=1 后运行脚本,会自动打开Playwright Inspector,可以单步调试、查看定位器、录制新脚本。

掌握了这些排查和优化技巧,你就能从容应对大部分自动化过程中遇到的问题,并写出高效、稳定的脚本。Python+Playwright的入门之旅到这里就告一段落了,但它的能力远不止于此,比如集成测试报告(Allure)、分布式执行、移动设备模拟等,都值得进一步探索。最重要的是,多动手、多实践,从解决一个个具体的自动化小任务开始,你会逐渐感受到它带来的效率提升。

更多推荐