1. 创建 Playwright 对象

方法1:上下文管理器(推荐)

from playwright.sync_api import sync_playwright

with sync_playwright() as pw:
    browser = pw.chromium.launch()
    page = browser.new_page()
    # 执行操作
    browser.close()

方法2:手动管理生命周期

from playwright.sync_api import sync_playwright

pw = sync_playwright().start()
browser = pw.chromium.launch()
page = browser.new_page()
# 执行操作
browser.close()
pw.stop()

2. 创建浏览器对象

# 有头浏览器(显示界面)
browser = pw.chromium.launch(headless=False)

# 无头浏览器(后台运行)
browser = pw.chromium.launch(headless=True)

# 其他浏览器
browser = pw.firefox.launch()
browser = pw.webkit.launch()

3. 创建 Page 对象

page = browser.new_page()

4. 打开网页

page.goto(url)
page.goto(url, wait_until="networkidle")  # 等待网络空闲
page.goto(url, timeout=30000)  # 设置超时时间(毫秒)

5. 获取网页信息

获取网页源代码

html_content = page.content()

获取网页 URL

current_url = page.url

获取网页标题

page_title = page.title()

6. 元素定位和交互

定位元素

# 返回 Locator 对象(懒加载)
locator = page.locator("xpath=//button[@id='submit']")
locator = page.locator("css=.button-class")
locator = page.locator("#element-id")
locator = page.locator(".element-class")

获取多个元素的操作

# 获取第一个元素
first_element = page.locator("//div[@class='item']").first

# 获取最后一个元素
last_element = page.locator("//div[@class='item']").last

# 获取指定索引的元素(0 开始)
nth_element = page.locator("//div[@class='item']").nth(2)

# 获取所有元素列表
all_elements = page.locator("//div[@class='item']").all()
for element in all_elements:
    print(element.inner_text())

7. 文本输入

使用 fill() - 清空后填充(推荐用于输入框)

page.locator("//input[@name='username']").fill("user123")

使用 type() - 逐字输入(模拟用户输入)

# 正常速度输入
page.locator("//input[@name='username']").type("user123")

# 添加延迟(每个字符之间延迟 100ms)
page.locator("//input[@name='username']").type("user123", delay=100)

8. 获取元素信息

获取元素文本内容

text = page.locator("//div[@class='content']").inner_text()

获取元素属性

# 获取指定属性值
href = page.locator("//a").get_attribute("href")
class_name = page.locator("//div").get_attribute("class")
id_value = page.locator("//button").get_attribute("id")

获取元素的 HTML 内容

html = page.locator("//div[@class='content']").inner_html()

9. 鼠标操作

点击元素

page.locator("//button[@id='submit']").click()

# 双击
page.locator("//button").dblclick()

# 右击
page.locator("//button").click(button="right")

鼠标低级操作

# 移动鼠标到指定坐标
page.mouse.move(x=100, y=200)

# 鼠标按下
page.mouse.down()

# 鼠标释放
page.mouse.up()

# 完整的拖拽操作示例
page.mouse.move(100, 100)
page.mouse.down()
page.mouse.move(200, 200)
page.mouse.up()

10. 拖拽操作

使用 drag_and_drop()(参数必须是选择器字符串)

# ❌ 错误做法(不能传 Locator 对象)
# source_locator = page.locator("//div[@id='source']")
# target_locator = page.locator("//div[@id='target']")
# page.drag_and_drop(source=source_locator, target=target_locator)  # 不行!

# ✅ 正确做法1:直接用选择器字符串
page.drag_and_drop(
    source="//div[@id='source']",
    target="//div[@id='target']"
)

使用 drag_to()(可以传 Locator 对象)⭐ 推荐

# ✅ 正确做法2:使用 Locator 对象的 drag_to 方法
source_locator = page.locator("//div[@id='source']")
target_locator = page.locator("//div[@id='target']")
source_locator.drag_to(target_locator)  # 可以传 Locator 对象!

关键区别:

  • page.drag_and_drop(source, target) → 参数必须是选择器字符串
  • locator.drag_to(target_locator) → 参数可以是 Locator 对象

11. 键盘操作

直接按键

# 单个按键
page.keyboard.press('Enter')
page.keyboard.press('Backspace')
page.keyboard.press('Delete')

# 组合键
page.keyboard.press('Control+a')  # 全选
page.keyboard.press('Control+c')  # 复制
page.keyboard.press('Control+v')  # 粘贴
page.keyboard.press('Control+z')  # 撤销
page.keyboard.press('Control+s')  # 保存
page.keyboard.press('Alt+Tab')    # 切换窗口

键盘输入

# 指定输入框中输入文本
page.keyboard.type("Hello World")

# 输入文本并带延迟(毫秒)
page.keyboard.type("Hello", delay=100)

12. 复制粘贴操作(重点解答)

⚠️ 常见误解

误解1: 能否先 Ctrl+A 复制,然后定位新标签,再 Ctrl+V 粘贴?

答案: 这取决于具体情况。复制的内容存储在「网页的虚拟剪贴板」中,只要页面没有刷新,虚拟剪贴板数据会保留。

可以工作的条件:

  • ✓ 源标签和目标标签在同一个页面
  • ✓ 中间没有刷新页面或导航
  • ✓ 没有 JavaScript 清空剪贴板
  • ✓ 两个标签都是可编辑的

会失败的情况:

  • ✗ 页面刷新了
  • ✗ 导航到其他页面
  • ✗ 目标标签是只读的(contentEditable=false)

⚠️ 常见误解2:能否用变量保存 press() 的返回值?

误解代码:

# ❌ 错误理解
data = page.keyboard.press('Control+c')  # 期望 data 里有复制的内容
print(data)  # 输出结果:None(空值)
# 然后页面刷新...
page.locator("//input[@id='target']").fill(data)  # ✗ 粘贴不了,data 是 None

为什么不行?

  1. press() 的返回值始终是 None

    def press(self, key: str) -> None:  # 返回值是 None
        # 只是发送一个"按键事件",不会返回任何数据
  2. Playwright 只是发送按键事件,不能拦截被复制的内容

    • page.keyboard.press('Control+c') 向浏览器发送"按 Ctrl+C"的信号
    • 网页的 JavaScript 接收这个事件,然后处理复制操作
    • 复制的内容存储在网页的虚拟剪贴板中,Playwright 无法读取
  3. 虚拟剪贴板在页面刷新时会被清空

    • 每个网页都有自己独立的虚拟剪贴板
    • 页面刷新 = 网页进程重启
    • 旧的虚拟剪贴板数据被清除
    • 新页面找不到这个数据

形象类比:

这就像你让朋友按下"复制"按钮,然后关闭了程序,
再打开时期望之前复制的东西还在一样 — 不可能的!

✅ 方案1:直接获取值再填充(最推荐,100% 可靠)

# 最简单、最稳定的方案!
source_text = page.locator("//input[@id='source']").input_value()
page.locator("//input[@id='target']").fill(source_text)

优点:

  • 直接从 DOM 获取值,100% 可靠
  • 无需依赖浏览器剪贴板
  • 速度快,代码简洁

✅ 方案2:使用键盘快捷键(需要谨慎)

# 点击源标签并复制
page.locator("//input[@id='source']").click()
page.keyboard.press('Control+a')
page.keyboard.press('Control+c')

# 立即点击目标标签并粘贴(中间不要有其他操作)
page.locator("//input[@id='target']").click()
page.keyboard.press('Control+v')

# 可选:等待粘贴完成
page.wait_for_timeout(500)

风险: 如果中间出现弹窗、页面更新等,可能导致粘贴失败

✅ 方案3:使用浏览器剪贴板 API(最稳定)

# 读取源标签的文本
source_text = page.locator("//input[@id='source']").input_value()

# 通过 JavaScript 将文本写入浏览器剪贴板
page.evaluate(f'navigator.clipboard.writeText("{source_text}")')

# 点击目标标签并粘贴
page.locator("//input[@id='target']").click()
page.keyboard.press('Control+v')

优点:

  • 利用浏览器原生剪贴板 API
  • 更稳定,跨页面有效
  • 可以和系统剪贴板交互

📊 三种方案对比

方案可靠性复杂度跨页面推荐度
方案1:直接填充⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
方案2:键盘快捷键⭐⭐⭐⭐⭐⭐⭐⭐
方案3:剪贴板 API⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

13. 其他常用操作

Frame(iframe)操作 ⭐ Playwright 的强项

获取 frame 对象

# 按 name 属性获取 frame
frame = page.frame(name="payment_frame")

# 按 url 获取 frame
frame = page.frame(url="https://example.com/iframe")

# 获取所有 frames
all_frames = page.frames

# 列出所有 frame 的信息
for frame in page.frames:
    print(f"Frame name: {frame.name}, URL: {frame.url}")

在 frame 中定位元素

# 方法1:先获取 frame,再定位(推荐用于多次操作)
frame = page.frame(name="payment_frame")
frame.locator("//input[@id='card_number']").fill("1234-5678-9012-3456")
frame.locator("//button[@id='submit']").click()

# 方法2:直接链式调用(推荐用于一次性操作)
page.frame(name="payment_frame") \
    .locator("//input[@id='card_number']") \
    .fill("1234-5678-9012-3456")

# 方法3:使用 frame_locator(按选择器查找 frame)
page.frame_locator("//iframe[@id='payment_frame']") \
    .locator("//input[@id='card_number']") \
    .fill("1234-5678-9012-3456")

嵌套 iframe 操作

# 多层 iframe 嵌套,直接链式调用
page.frame(name="outer") \
    .frame(name="inner") \
    .frame(name="deepest") \
    .locator("//button[@id='submit']") \
    .click()

# 或者分步骤写(更易读)
outer_frame = page.frame(name="outer")
inner_frame = outer_frame.frame(name="inner")
inner_frame.locator("//input[@id='nested_input']").fill("数据")

# 或者混合方式
outer = page.frame(name="outer")
outer.frame(name="inner") \
    .locator("//input[@id='nested_input']") \
    .fill("数据")
outer.locator("//button[@class='submit']").click()

获取 frame 中元素的信息

frame = page.frame(name="payment_frame")

# 获取元素文本
text = frame.locator("//span[@id='price']").inner_text()

# 获取元素属性
href = frame.locator("//a[@id='link']").get_attribute("href")

# 获取元素的 HTML
html = frame.locator("//div[@id='content']").inner_html()

# 获取输入框的值
value = frame.locator("//input[@id='username']").input_value()

在 frame 中执行 JavaScript

frame = page.frame(name="payment_frame")

# 执行 JavaScript 代码
result = frame.evaluate("1 + 1")
print(result)  # 2

# 获取 DOM 信息
count = frame.evaluate("document.querySelectorAll('input').length")

# 修改 DOM
frame.evaluate("document.getElementById('price').innerText = '¥99'")

✅ 关键优势:无需手动切换上下文

# Playwright 会自动管理 frame 上下文
# 这段代码在 frame 中操作...
page.frame(name="payment_frame") \
    .locator("//input[@id='card_number']") \
    .fill("1234-5678-9012-3456")

# ...然后直接回到主页面操作,无需手动切换!
page.locator("//h1").inner_text()  # ✓ 自动就在主页面

# Selenium 就需要这样做:
# browser.switch_to.frame("payment_frame")
# browser.find_element(By.ID, "card_number").send_keys("...")
# browser.switch_to.default_content()  # 必须手动切换回来!
# browser.find_element(By.TAG_NAME, "h1").text

等待操作

⭐ 方法1:等待元素状态变化(page.wait_for_selector)

这是你提到的方法!它有 4 个参数:

方法签名:

page.wait_for_selector(
    selector: str,              # 选择器(必填)
    state: str = None,          # 元素状态(可选)
    timeout: float = None,      # 超时时间(可选,单位:秒)
    strict: bool = None         # 严格模式(可选)
) -> Optional[ElementHandle]

参数详解:

参数说明默认值单位
selectorCSS 选择器或 XPath 表达式(必填)--
state元素状态,有 4 个值(见下表)"attached"-
timeout等待超时时间None(30秒) ⚠️
strict严格模式(选择器必须恰好匹配 1 个元素)None-

state 参数的 4 个值:

含义使用场景
"attached"元素在 DOM 中(默认)元素加载到页面
"visible"元素可见(附加且不隐藏)等待元素显示 ⭐ 最常用
"hidden"元素隐藏等待加载动画消失
"detached"元素从 DOM 完全移除等待弹窗关闭

返回值:

  • ElementHandle 对象(如果 state="visible" 或 "attached")
  • None(如果 state="hidden" 或 "detached")

使用示例:

# 最基础:等待元素出现
page.wait_for_selector("//button[@id='submit']")

# 等待元素可见(最常用)
element = page.wait_for_selector("//button[@id='submit']", state="visible")
element.click()  # 使用返回的 ElementHandle

# 等待元素消失
page.wait_for_selector("//loading", state="hidden")

# 自定义超时时间(单位:秒)⚠️  注意不是毫秒!
page.wait_for_selector("//button", state="visible", timeout=10)  # 10 秒

# 严格模式:选择器必须恰好匹配一个元素
page.wait_for_selector("//button[@class='submit']", strict=True)

# 获取返回的元素并操作
element = page.wait_for_selector("//input[@id='username']", state="visible")
element.fill("user123")
element.press("Enter")

方法2:等待元素状态变化(locator().wait_for)

# 等待元素在 DOM 中出现(默认)
page.locator("//button[@id='submit']").wait_for()

# 等待元素可见(推荐)
page.locator("//button[@id='submit']").wait_for(state="visible")

# 等待元素可见,自定义超时时间(10秒,单位:毫秒)⚠️  注意是毫秒!
page.locator("//button[@id='submit']").wait_for(state="visible", timeout=10000)

# 等待元素消失(隐藏)
page.locator("//loading-spinner").wait_for(state="hidden")

# 等待元素从 DOM 完全移除
page.locator("//modal").wait_for(state="detached")

注意区别:

  • page.wait_for_selector() 返回 ElementHandle,可以继续操作
  • locator().wait_for() 返回 None,不能链式调用
  • timeout 单位不同: wait_for_selector()locator().wait_for()毫秒

方法3:等待页面加载完成

# 等待页面加载完成(推荐)
page.wait_for_load_state("load")

# 等待 DOM 内容加载完成(较快)
page.wait_for_load_state("domcontentloaded")

# 等待网络空闲(所有网络请求完成)
page.wait_for_load_state("networkidle")

# 自定义超时时间
page.wait_for_load_state("load", timeout=10)

# 在 goto 中使用更方便(推荐)
page.goto(url, wait_until="networkidle")

方法4:等待指定时间(不推荐)

# 等待 2 秒(毫秒为单位)
page.wait_for_timeout(2000)

# ⚠️  尽量避免使用!应该用等待方法而不是固定延迟

方法5:等待 JavaScript 条件成立

# 等待直到元素文本变为特定值
page.wait_for_function(
    "document.getElementById('status').textContent === '加载完成'"
)

# 等待直到某个 JavaScript 变量存在
page.wait_for_function("typeof window.myVar !== 'undefined'")

# 等待直到满足条件(自定义超时)
page.wait_for_function(
    "document.querySelectorAll('.item').length > 10",
    timeout=5
)

方法6:等待页面导航完成

# 简单等待导航
page.locator("//a[@href='/next']").click()
page.wait_for_navigation()

# 等待导航到特定 URL
page.wait_for_navigation(url="**/success")

# 使用 with 语句(最推荐)
with page.expect_navigation():
    page.locator("//a").click()

📊 wait_for_selector vs locator().wait_for 对比

特性wait_for_selector()locator().wait_for()
返回值ElementHandleNoneNone
可以获取元素✅ 可以❌ 不能
可以继续操作返回值✅ 可以❌ 不能
参数:state✅ 支持✅ 支持
参数:timeout 单位 ⚠️毫秒
参数:strict✅ 支持❌ 不支持
推荐度⭐⭐⭐⭐⭐⭐⭐⭐⭐
使用场景需要获取元素时现代推荐方式

💡 深入理解:ElementHandle vs Locator(完全纠正)

重要纠正:我之前的说法完全错误!

根据 Playwright 源代码验证,ElementHandle 和 Locator 的操作方法都返回 None

实际的返回值(通过代码验证):

# Locator 的操作方法签名
Locator.click(...) -> None         # 返回 None!不是 Locator!
Locator.fill(...) -> None          # 返回 None!不是 Locator!
Locator.press(...) -> None         # 返回 None!不是 Locator!
Locator.type(...) -> None          # 返回 None!不是 Locator!

# ElementHandle 的操作方法
ElementHandle.click(...) -> None
ElementHandle.fill(...) -> None
ElementHandle.press(...) -> None

这意味着什么?

# ❌ Locator 不能链式调用操作方法
page.locator("//button").click().fill()  # 错误!click() 返回 None
page.locator("//button").click().is_visible()  # 错误!

# ❌ ElementHandle 也不能链式调用操作方法
element = page.wait_for_selector("//button")
element.click().fill()  # 错误!click() 返回 None

# ✅ 但单个操作是可以的
page.locator("//button").click()  # ✅ 可以
element.click()  # ✅ 可以

那 Locator 支持什么样的链式?

Locator 支持的是「定位器链式」(通过定位方法返回新的 Locator),不是「操作方法链式」:

# ✅ 定位器链式(返回新的 Locator 对象)
page.locator("div").first.click()              # first 返回 Locator
page.locator("div").last.click()               # last 返回 Locator
page.locator("div").nth(0).click()             # nth() 返回 Locator
page.locator("div").filter(has=...).click()    # filter() 返回 Locator

# ❌ 操作方法链式(都返回 None)
page.locator("div").click().fill()      # click() 返回 None,错误!
page.locator("div").fill().type()       # fill() 返回 None,错误!

ElementHandle 是什么?

  • ElementHandle 是一个对象,代表网页上的一个具体 DOM 元素
  • page.wait_for_selector() 找到元素时,就返回这个 ElementHandle
  • ElementHandle 和 Locator 的使用方式基本相同

ElementHandle 可以做的操作:

element = page.wait_for_selector("//input[@id='username']")

element.click()              # 点击(返回 None)
element.fill("user123")      # 填充文本(返回 None)
element.type("text")         # 输入文本(返回 None)
element.get_attribute("href")  # 获取属性(返回字符串)
element.inner_text()         # 获取文本(返回字符串)
element.press("Enter")       # 按键(返回 None)
element.hover()              # 悬停(返回 None)
element.screenshot()         # 截图(返回图片)

对比:wait_for_selector vs locator

特性wait_for_selectorlocator
返回值ElementHandleLocator
基本操作(click、fill)✅ 支持✅ 支持
操作方法链式❌ 不支持❌ 不支持
定位器链式(first、last、nth)❌ 不支持✅ 支持
自动等待❌ 需要手动 wait_for()✅ 内置自动等待
代码简洁度⭐⭐⭐⭐⭐⭐⭐⭐

为什么还是推荐用 Locator?

  1. 自动等待 - 不需要显式调用 wait_for_selector()
  2. 定位器灵活 - 支持 first、last、nth、filter 等来细化定位
  3. 代码简洁 - 一行代码 vs 两行代码
  4. 现代设计 - 这是 Playwright 官方推荐的 Web-First 方式

正确的使用方式:

# ✅ 推荐:用 Locator
page.locator("//button").click()
page.locator("//input").fill("text")

# ✅ Locator 定位器链式(unique feature)
page.locator("div").first.click()
page.locator("div").last.fill("text")
page.locator("table tr").nth(2).click()

# ✅ 可以用 wait_for_selector,但需要分开写
element = page.wait_for_selector("//button")
element.click()

💡 最直白的解释:element.click() vs page.wait_for_selector().click()

element = page.wait_for_selector("//button")
# element 就是一个 ElementHandle 对象
element.click()  # ✅ 这能工作

为什么能工作?

element.click()

执行过程:
1️⃣  element 已经是 ElementHandle 对象
2️⃣  ElementHandle.click() 执行点击动作
3️⃣  返回 None(但我们不继续使用它)
✅ 完成!

为什么 page.wait_for_selector().click().is_visible() 不行?

page.wait_for_selector("//button").click().is_visible()

执行过程:
1️⃣  page.wait_for_selector("//button") → 返回 ElementHandle
2️⃣  ElementHandle.click() → 返回 None
3️⃣  None.is_visible() → ❌ 错误!None 没有 .is_visible() 方法

对比 Locator 的定位器链式:

page.locator("div").first.click()

执行过程:
1️⃣  page.locator("div") → 返回 Locator
2️⃣  Locator.first → 返回 Locator(注意!这是属性,不是方法)
3️⃣  Locator.click() → 可以工作

总结:

  • page.wait_for_selector().click() 可以工作
  • page.wait_for_selector().click().is_visible() 不行(返回 None)
  • page.locator().click().fill() 也不行(返回 None)
  • page.locator().first.click() 可以工作(first 返回 Locator)

元素可见性判断

is_visible = page.locator("//button").is_visible()
is_enabled = page.locator("//button").is_enabled()

获取元素计数

count = page.locator("//div[@class='item']").count()

选择框操作

# 选择 select 框的选项
page.locator("//select[@name='country']").select_option("China")
page.locator("//select[@name='country']").select_option(value="cn")

复选框操作

# 勾选
page.locator("//input[@type='checkbox']").check()

# 取消勾选
page.locator("//input[@type='checkbox']").uncheck()

# 切换状态
page.locator("//input[@type='checkbox']").click()

14. 快速对比:Playwright vs Selenium

基础操作对比

操作PlaywrightSelenium
获取源代码page.content()driver.page_source
点击page.locator().click()element.click()
输入page.locator().fill()element.send_keys()
等待内置智能等待需要显式等待
速度⚡ 更快较慢
易用性⭐⭐⭐⭐⭐⭐⭐⭐⭐

Frame(iframe)操作对比 - Playwright 的巨大优势 ⭐

HTML 结构示例

<html>
  <body>
    <h1>主页面内容</h1>
    <iframe name="payment_frame" id="iframe-1">
      <form>
        <input id="card_number" placeholder="输入卡号">
        <button>提交</button>
      </form>
    </iframe>
  </body>
</html>

❌ Selenium 的做法(需要手动切换上下文,冗长且容易出错)

from selenium import webdriver
from selenium.webdriver.common.by import By

browser = webdriver.Chrome()
browser.get("http://example.com")

# ❌ 直接找不到,因为还在主页面
try:
    element = browser.find_element(By.ID, "card_number")
except:
    print("❌ 找不到!因为还没进入 iframe")

# 必须先切换到 iframe
browser.switch_to.frame("payment_frame")  # 按 name 切换
# 或者 browser.switch_to.frame(0)  # 按索引
# 或者 browser.switch_to.frame(browser.find_element(By.ID, "iframe-1"))

# 现在才能找到
element = browser.find_element(By.ID, "card_number")
element.send_keys("1234-5678-9012-3456")

# ⚠️ 必须手动切换回主页面
browser.switch_to.default_content()

# 现在回到了主页面
h1 = browser.find_element(By.TAG_NAME, "h1")
print(h1.text)  # "主页面内容"

Selenium 的问题:

  • ❌ 需要手动切换上下文(switch_to.frame()
  • ❌ 需要手动切换回来(switch_to.default_content()
  • ❌ 容易忘记切换,导致找不到元素
  • ❌ 代码不够流畅,上下文管理复杂
  • ❌ 嵌套 iframe 时特别麻烦

✅ Playwright 的做法(直接链式调用,优雅!)

from playwright.sync_api import sync_playwright

with sync_playwright() as pw:
    browser = pw.chromium.launch()
    page = browser.new_page()
    page.goto("http://example.com")
    
    # 🎯 方法1:按 name 进入 iframe(最常用)
    frame = page.frame(name="payment_frame")
    frame.locator("//input[@id='card_number']").fill("1234-5678-9012-3456")
    
    # 🎯 方法2:按选择器进入(用 frame_locator)
    frame = page.frame_locator("//iframe[@id='iframe-1']")
    frame.locator("//input[@id='card_number']").fill("1234-5678-9012-3456")
    
    # 🎯 方法3:直接链式调用(最推荐,一行代码搞定!)
    page.frame(name="payment_frame") \
        .locator("//input[@id='card_number']") \
        .fill("1234-5678-9012-3456")
    
    # 🎯 方法4:多层 iframe 嵌套也很简单
    page.frame(name="outer_frame") \
        .frame(name="inner_frame") \
        .locator("//button[@id='submit']") \
        .click()
    
    # ✅ 不需要切换回来!Playwright 自动管理上下文
    page.locator("//h1").inner_text()  # ✓ 自动就在主页面上

Playwright 的优势:

  • ✅ 无需手动切换上下文(自动管理)
  • ✅ 支持链式调用,代码优雅简洁
  • ✅ 自动管理 frame 生命周期
  • ✅ 嵌套 iframe 轻而易举
  • ✅ 更容易读和维护

🌲 复杂场景:嵌套 iframe 的对比

❌ Selenium(冗长且容易出错)

# 进入嵌套 iframe
browser.switch_to.frame("outer")
browser.switch_to.frame("inner")
element = browser.find_element(By.ID, "nested_input")
element.send_keys("数据")

# 需要逐层切回来,很容易出错
browser.switch_to.default_content()  # 回到主页面
browser.switch_to.frame("outer")  # 再进去
button = browser.find_element(By.XPATH, "//button[text()='按钮A']")
button.click()
browser.switch_to.default_content()  # 再切回来

✅ Playwright(简洁优雅)

# 进入嵌套 iframe 并输入(一行链式调用)
page.frame(name="outer") \
    .frame(name="inner") \
    .locator("//input[@id='nested_input']") \
    .fill("数据")

# 点击外层按钮(无需切换,自动在正确的上下文)
page.frame(name="outer") \
    .locator("//button[text()='按钮A']") \
    .click()

# 或者保存中间变量,更易读
outer = page.frame(name="outer")
outer.frame(name="inner").locator("//input[@id='nested_input']").fill("数据")
outer.locator("//button[text()='按钮A']").click()

Frame 操作详细对比表

维度SeleniumPlaywright
进入 iframeswitch_to.frame()page.frame()
切换回主页面switch_to.default_content()无需(自动)
链式调用❌ 不支持✅ 支持
多层 iframe麻烦,需要多次切换简洁,直接链式调用
代码行数更多(包含切换逻辑)更少(简洁优雅)
易错性高(容易忘记切换)低(自动管理)
可读性⭐⭐⭐⭐⭐⭐⭐⭐
维护性⭐⭐⭐⭐⭐⭐⭐⭐

15. 完整示例

from playwright.sync_api import sync_playwright

def test_web_automation():
    with sync_playwright() as pw:
        # 启动浏览器
        browser = pw.chromium.launch(headless=False)
        page = browser.new_page()
        
        # 打开网页
        page.goto("https://example.com")
        print(f"页面标题: {page.title()}")
        print(f"页面 URL: {page.url}")
        
        # 定位输入框并输入
        page.locator("//input[@name='search']").fill("Playwright")
        
        # 点击搜索按钮
        page.locator("//button[@type='submit']").click()
        
        # 等待结果加载
        page.wait_for_timeout(2000)
        
        # 获取搜索结果
        results = page.locator("//div[@class='result']").all()
        print(f"找到 {len(results)} 个结果")
        
        for result in results:
            print(result.inner_text())
        
        # 获取网页源代码
        html = page.content()
        print(f"源代码长度: {len(html)}")
        
        # 关闭浏览器
        browser.close()

if __name__ == "__main__":
    test_web_automation()

16. 常见错误排查

错误原因解决方案
TimeoutError元素加载超时增加 wait_for_timeout() 或检查选择器
选择器找不到元素XPath/CSS 语法错误用浏览器开发者工具验证选择器
鼠标操作失败元素可能不可见先滚动到元素位置或等待元素显示
拖拽不工作坐标不准确使用 drag_to() 而不是低级鼠标操作

标签: none

添加新评论