首页
/ Playwright Frame 类完全指南:掌握页面 iframe 树、定位、求值与导航的核心 API

Playwright Frame 类完全指南:掌握页面 iframe 树、定位、求值与导航的核心 API

2026-09-06 18:50:11作者:裴麒琰

在 Playwright 中,Frame(框架)是页面 DOM 树的组织单元:一个页面由主框架(main frame)与若干子框架(child frame,通常由 <iframe> 产生)构成。无论是处理第三方广告 iframe、富文本编辑器内嵌页,还是多层嵌套的业务系统,你都需要直接面向 Frame 编程。本指南基于 Playwright 官方 API 文档 docs/src/api/class-frame.md 展开,并结合仓库内 客户端实现 深入剖析其底层原理。读完本文,你将掌握框架树的遍历、iframe 内定位、DOM 求值、表单操作、状态等待与导航同步等一整套实战方案。

理解 Frame:页面中的框架树与生命周期

主框架与子框架的树形结构

Page 在任意时刻都会通过两个方法暴露当前框架树:

  • [method: Page.mainFrame]——返回页面的主框架,它是顶层文档所在的 Frame 对象;
  • [method: Frame.childFrames]——返回当前框架的所有子框架数组。

客户端实现 中,Frame 类内部维护了 _parentFrame_childFramesSet<Frame>)等字段,并持有 _url_name_detached 状态,用来精确刻画框架树。相应地,Pagepage.ts 中通过 _onFrameAttached / _onFrameDetached 维护帧集合,保证父子关系与主框架映射的一致。

三个决定 Frame 生命周期的事件

框架对象的生命周期由发布在 Page 对象上的三个事件控制:

事件 触发时机 备注
[event: Page.frameAttached] 框架被挂载到页面时 一个 Frame 只能被挂载一次
[event: Page.frameNavigated] 框架提交导航到不同 URL 时 提交即生效
[event: Page.frameDetached] 框架从页面分离时 一个 Frame 只能被分离一次

完整示例:递归转储框架树

下面的四语言示例演示了从主框架出发递归打印全部框架 URL 的方法,是理解框架树最直接的起点:

const { firefox } = require('playwright');  // 或 'chromium' / 'webkit'

(async () => {
  const browser = await firefox.launch();
  const page = await browser.newPage();
  await page.goto('https://www.google.com/chrome/browser/canary.html');
  dumpFrameTree(page.mainFrame(), '');
  await browser.close();

  function dumpFrameTree(frame, indent) {
    console.log(indent + frame.url());
    for (const child of frame.childFrames())
      dumpFrameTree(child, indent + '  ');
  }
})();
from playwright.sync_api import sync_playwright, Playwright

def run(playwright: Playwright):
    firefox = playwright.firefox
    browser = firefox.launch()
    page = browser.new_page()
    page.goto("https://www.theverge.com")
    dump_frame_tree(page.main_frame, "")
    browser.close()

def dump_frame_tree(frame, indent):
    print(indent + frame.name + '@' + frame.url)
    for child in frame.child_frames:
        dump_frame_tree(child, indent + "    ")

with sync_playwright() as playwright:
    run(playwright)
import asyncio
from playwright.async_api import async_playwright, Playwright

async def run(playwright: Playwright):
    firefox = playwright.firefox
    browser = await firefox.launch()
    page = await browser.new_page()
    await page.goto("https://www.theverge.com")
    dump_frame_tree(page.main_frame, "")
    await browser.close()

def dump_frame_tree(frame, indent):
    print(indent + frame.name + '@' + frame.url)
    for child in frame.child_frames:
        dump_frame_tree(child, indent + "    ")

async def main():
    async with async_playwright() as playwright:
        await run(playwright)
asyncio.run(main())
import com.microsoft.playwright.*;

public class Example {
  public static void main(String[] args) {
    try (Playwright playwright = Playwright.create()) {
      BrowserType firefox = playwright.firefox();
      Browser browser = firefox.launch();
      Page page = browser.newPage();
      page.navigate("https://www.google.com/chrome/browser/canary.html");
      dumpFrameTree(page.mainFrame(), "");
      browser.close();
    }
  }
  static void dumpFrameTree(Frame frame, String indent) {
    System.out.println(indent + frame.url());
    for (Frame child : frame.childFrames()) {
      dumpFrameTree(child, indent + "  ");
    }
  }
}
using Microsoft.Playwright;
using System;
using System.Threading.Tasks;

class FrameExamples
{
    public static async Task Main()
    {
        using var playwright = await Playwright.CreateAsync();
        await using var browser = await playwright.Firefox.LaunchAsync();
        var page = await browser.NewPageAsync();

        await page.GotoAsync("https://www.bing.com");
        DumpFrameTree(page.MainFrame, string.Empty);
    }

    private static void DumpFrameTree(IFrame frame, string indent)
    {
        Console.WriteLine($"{indent}{frame.Url}");
        foreach (var child in frame.ChildFrames)
            DumpFrameTree(child, indent + " ");
    }
}

在 JS 与 Python 示例中可以看到,Python 的 main_frame 同时携带 nameurl 两个属性,便于在打印时区分框架。

关于 iframe 的正确打开方式

官方文档反复强调一个现代定位原则:当页面操作与 iframe 相关时,优先使用 page.frameLocator(),它无需手动获取 Frame 对象即可完成“进入 iframe 再定位”的链路。真正的 Frame 对象用于以下场景:

  • 需要遍历框架树、或对某个具体框架执行整段 JS 求值;
  • 需要监听框架的挂载/分离/导航事件;
  • 需要把 FrameLocator 之外的、要求精确到某个已存在的 Frame 实例的操作完成。

Frame 上查找与定位元素:从 Locator 到 FrameLocator

frameLocator:一步进入 iframe 定位

[method: Frame.frameLocator](自 v1.17 起)返回 FrameLocator,用于在 iframe 内部定位元素。它有两种调用方式:

带选择器——限定在匹配的 iframe 内查找:

const locator = frame.frameLocator('#my-iframe').getByText('Submit');
await locator.click();
locator = frame.frame_locator("#my-iframe").get_by_text("Submit")
locator.click()
Locator locator = frame.frameLocator("#my-iframe").getByText("Submit");
locator.click();

不带选择器——搜索从当前 frame 或其中任意 iframe 开始,无需先逐一定位 iframe:

const locator = frame.frameLocator().getByRole('button');
await locator.click();
var locator = frame.FrameLocator().GetByRole(AriaRole.Button);
await locator.ClickAsync();

值得注意的边界行为是:当 frameLocator() 不带选择器时,搜索起始范围扩大,但其余定位器解析仍限定在单个 frame 内;若最终匹配跨越了多个 frame,会抛出错误。选择器参数为空时,客户端在 frame.ts 中实际使用了 kAnyFrameSelector(任意 frame 选择器),这正是“当前 frame 或其中任意 iframe 均可”语义的来源。

locator 与 getBy* 系列

[method: Frame.locator](自 v1.14 起)基于选择器创建根定位器,支持 hashasTexthasNot(v1.33)、hasNotText(v1.33)等过滤选项;getByRole / getByText / getByLabel / getByPlaceholder / getByAltText / getByTitle / getByTestId(均自 v1.27 起)返回按语义角色与可访问性文本定位的 Locator。从实现上看,getByText 等调用 locatorUtils.ts 中的选择器生成函数把语义条件编译为底层引擎选择器,并由 locator.ts 提供自动等待能力。

更多细节可参见 locators 指南

传统 DOM 查询 API(已不推荐)

Playwright 建议用 Locator 取代如下方法,因其具备自动等待与可重试性:

  • [method: Frame.querySelector](JS 别名 $)——匹配单个元素,找不到返回 null;使用 ElementHandle 被明确 caution 警告(参见 frame.ts 的委托实现);
  • [method: Frame.querySelectorAll](JS 别名 $$)——匹配全部元素,无匹配返回空数组;
  • [method: Frame.$eval](即 evalOnSelector)——找到元素后把它作为第一个参数传给表达式;若元素不存在则抛错;
  • [method: Frame.$$eval](即 evalOnSelectorAll)——把匹配的元素数组传给表达式。

例如用 $$eval 判断 div 数量是否不小于阈值:

const divsCounts = await frame.$$eval('div', (divs, min) => divs.length >= min, 10);
divs_counts = frame.eval_on_selector_all("div", "(divs, min) => divs.length >= min", 10)
boolean divsCounts = (boolean) page.evalOnSelectorAll("div", "(divs, min) => divs.length >= min", 10);

这些求值方法同样支持向表达式额外传参 argEvaluationArgument 类型),参看 frame.tsserializeArgument 的处理。

Frame 内执行 JS:evaluate 与 evaluateHandle

evaluate:返回值可序列化

[method: Frame.evaluate] 在 frame 上下文中执行函数或字符串表达式,并返回其求值结果:

  • 函数返回 Promise 时,Playwright 会等待其 resolve 后返回结果;
  • 返回非 Serializable 值时返回 undefined;除 JSON 外还额外支持 -0NaNInfinity-Infinity

函数形式并传参:

const result = await frame.evaluate(([x, y]) => {
  return Promise.resolve(x * y);
}, [7, 8]);
console.log(result); // 打印 56
result = frame.evaluate("([x, y]) => Promise.resolve(x * y)", [7, 8])
print(result) # 打印 56

字符串表达式形式:

console.log(await frame.evaluate('1 + 2')); // 打印 3
x = 10
print(frame.evaluate(f"1 + {x}")) # 打印 11

传入 ElementHandle 作为参数(需注意用完 dispose):

const bodyHandle = await frame.evaluateHandle('document.body');
const html = await frame.evaluate(([body, suffix]) =>
  body.innerHTML + suffix, [bodyHandle, 'hello'],
);
await bodyHandle.dispose();

实现代码 可以看到,evaluate 通过通道调用 evaluateExpression,并以 isFunction 区分函数与表达式;参数经 serializeArgument 序列化,结果经 parseResult 还原。

evaluateHandle:保留对象引用

[method: Frame.evaluateHandle] 与 evaluate 的唯一区别是返回 JSHandle(而非序列化值),因此可以拿到底层 DOM 对象/函数/window 的实时引用,用于后续继续传入其他求值调用:

// window 对象的 handle
const aWindowHandle = await frame.evaluateHandle(() => Promise.resolve(window));
// 字符串形式也可
const aHandle = await frame.evaluateHandle('document'); // document 的 handle
// JSHandle 亦可作为参数传给 evaluateHandle
const resultHandle = await frame.evaluateHandle(([body, suffix]) =>
  body.innerHTML + suffix, [aHandle, 'hello']);
console.log(await resultHandle.jsonValue());
await resultHandle.dispose();
a_window_handle = await frame.evaluate_handle("Promise.resolve(window)")
a_handle = await frame.evaluate_handle("document") # document 的 handle
print(await a_handle.json_value())
await a_handle.dispose()

对应实现见 frame.ts,返回的 JSHandle 应通过 dispose()/DisposeAsync() 释放,避免句柄泄漏。

注入标签与读取内容:addScriptTag / addStyleTag 与文本提取

注入脚本与样式

[method: Frame.addScriptTag] 向 frame 注入 <script> 标签,等脚本 onload 触发或内容注入完成后返回对应 ElementHandle;支持三类来源与一个类型参数:

选项 类型 说明
url string 待加载脚本的 URL
path path 本地 JS 文件路径,相对路径按当前工作目录解析
content string 要注入的原始 JS 文本
type string 脚本类型,传 'module' 可加载 ES6 模块

底层实现值得注意:当传入 path 时,客户端会先用 fs.promises.readFile 读取文件,再通过 addSourceUrlToScript//# sourceURL 追加到内容中(见 frame.ts),这样后续调试时能定位到原始文件。

[method: Frame.addStyleTag] 同理注入样式:传 url 生成 <link rel="stylesheet">,传 content 生成 <style type="text/css">;若传 path,读取 CSS 后会追加 /*# sourceURL=... */ 注释(见 frame.ts)。

内容与标题读取

  • [method: Frame.content]——返回含 doctype 的整段 HTML;
  • [method: Frame.setContent]——用 HTML 覆盖 frame 文档,底层调用 document.write(),因此继承其全部特性与行为,并可配合 timeoutwaitUntil 选项;
  • [method: Frame.title]——返回页面标题。

针对“选中的元素”读取文本与 HTML,有 textContentinnerTextinnerHTMLgetAttribute(name)inputValue 五个常用方法(返回可能为 null,例如元素不存在时 textContent/getAttribute 返回 null)。注意 inputValue 只适用于 <input><textarea><select>,若元素位于带关联 control<label> 内则读取关联控件值。

表单交互与操作类 API

Frame 提供了一整套以 CSS 选择器为入参的传统操作 API。官方已将这些方法标注为 discouraged(建议改用 Locator 同名方法),但在理解旧代码、快速脚本或框架测试中仍然常用,其语义与 Locator 版本一致:先找元素(无则等待挂载)→ 通过可操作性(actionability)检查(除非设置 force,且检查期间元素被分离则整体重试)→ 滚动进入视口 → 通过 Page.mouse 执行点击 → 验证结果。若在 timeout 内未完成,抛出 TimeoutError;传 timeout: 0 表示关闭等待。

方法 核心语义 特有参数
click(selector) 单击元素,等待由点击触发的导航完成 button/clickCount/delay/modifiers/position/trial
dblclick(selector) 双击,注意会派发两次 click 与一次 dblclick 同上
check(selector) / uncheck(selector) 勾选/取消勾选复选框或单选钮(已处于目标状态则直接返回) 通用可操作性参数
setChecked(selector, checked) 根据布尔值选择调用 check/uncheck checked
tap(selector) 通过 Page.touchscreen 轻点元素 需 context hasTouch: true
hover(selector) 悬停到元素中心或指定位置 modifiers
dragAndDrop(source, target) 从源拖放到目标 sourcePosition/targetPosition/steps(v1.57)
focus(selector) 聚焦元素(无匹配则等待出现)
fill(selector, value) 等待 → 聚焦 → 填充 → 触发 input 事件;传空串可清空 value
type(selector, text) 逐字符派发 keydown/keypress+input/keyup(已弃用,建议 fill/pressSequentially delay 默认 0
press(selector, key) 对元素按键 key/delay
selectOption(selector, values) 选择 <select> 选项,返回成功选择的 value 数组,完成后触发 change+input 支持 value/label/index 或 SelectOption
setInputFiles(selector, files) <input type=file> 设置本地路径或文件载荷 相对路径按 cwd 解析,传空数组清空
dispatchEvent(selector, type, eventInit) 同步派发指定 DOM 事件,不受可见性影响 见下文

键位与修饰符的细节

presskey 可以是 keyboardEvent.key 值或单个字符。常用键示例:F1-F12Digit0-Digit9KeyA-KeyZBackquoteMinusEqualBackslashBackspaceTabDeleteEscapeArrowDownEndEnterHomeInsertPageDownPageUpArrowRightArrowUp 等。

修饰键支持 ShiftControlAltMetaShiftLeftControlOrMetaControlOrMeta 在 Windows/Linux 解析为 Control,macOS 上解析为 Meta)。组合快捷键如 "Control+o""Control+Shift+T" 亦受支持,此时修饰键保持按下直到后续键按下。

dispatchEvent:绕过可操作性派发事件

[method: Frame.dispatchEvent] 不关心元素可见性,直接构造事件并派发,等价于 element.click() 等。事件默认 composedcancelable 且会冒泡。支持的事件类型包括 DeviceMotionEventDeviceOrientationEventDragEventEventFocusEventKeyboardEventMouseEventPointerEventTouchEventWheelEvent,初始化属性以 eventInit 传入。

await frame.dispatchEvent('button#submit', 'click');

也可以把 JSHandle 作为属性值传入,以携带实时对象(例如拖拽所需的 DataTransfer,注意它只能在 Chromium 与 Firefox 中创建):

const dataTransfer = await frame.evaluateHandle(() => new DataTransfer());
await frame.dispatchEvent('#source', 'dragstart', { dataTransfer });
data_transfer = frame.evaluate_handle("new DataTransfer()")
frame.dispatch_event("#source", "dragstart", { "dataTransfer": data_transfer })

状态断言类方法

  • isChecked(selector)——元素是否为勾选态(非复选框/单选钮则抛错);
  • isDisabled(selector) / isEnabled(selector)——判断禁用/可用;
  • isEditable(selector)——判断是否可编辑;
  • isHidden(selector) / isVisible(selector)——判断可见性;选择器无匹配元素分别视为“隐藏”与“不可见”;这两个方法的 timeout 参数已被废弃(不等待、立即返回),且从 frame.ts 可见其调用使用 kNoTimeout

导航、加载状态与等待 API

goto:导航并获取响应

[method: Frame.goto](Java 别名为 navigate)让 frame 导航到指定 URL,返回主资源响应(多次重定向时以最后一次重定向的响应为准)。

以下情况会抛错:SSL 错误(如自签名证书)、目标 URL 非法、超过 timeout、服务器无响应/不可达、主资源加载失败。但只要返回合法 HTTP 状态码(含 404、500)就不会抛错,状态码可通过 Response.status() 获取。

两个值得记住的特例(成功但返回 null):导航到 about:blank;或带不同 hash 导航到同一 URL。另外,Headless 模式不支持导航到 PDF 文档(受上游 Chromium issue 限制)。

参数速览:

选项 默认 说明
url 目标 URL,需包含 scheme(如 https://
waitUntil load 等待的加载状态,可选 load/domcontentloaded/networkidle/commit
timeout 依上下文导航超时 超过则抛错;0 表示禁用
referer Referer 请求头,优先于 Page.setExtraHTTPHeaders 设置的 referer

frame.ts 中,goto 通过 verifyLoadState 校验 waitUntil 取值,合法集合为 load|domcontentloaded|networkidle|commit(见同文件 verifyLoadState)。

waitForLoadState / waitForURL:可靠的导航同步

  • [method: Frame.waitForLoadState]——等待 frame 达到某个加载状态(默认 load)。调用时必须已经提交导航;若当前文档已达到目标状态则立即返回。多数场景下 Playwright 会在动作前自动等待,因此该方法通常不必要。
  • [method: Frame.waitForURL](v1.11)——等待 frame 导航到给定 URL,支持 glob 模式(**/target.html)与正则:
await frame.click('a.delayed-navigation'); // 点击间接触发导航
await frame.waitForURL('**/target.html');
frame.click("a.delayed-navigation")
frame.wait_for_url("**/target.html")

waitForNavigation:已弃用的竞态来源

[method: Frame.waitForNavigation](Java 的导航回调、Python 的 expect_navigation、C# 的 RunAndWaitForNavigation)已弃用,原因是它“天生有竞态”——必须先注册等待再触发导航。官方推荐改用 waitForURL。仍会见到它出现在旧测试中:

const navigationPromise = page.waitForNavigation(); // 先启动等待(注意此处无 await)
await page.getByText('Navigate after timeout').click();
await navigationPromise;
with frame.expect_navigation():
    frame.click("a.delayed-navigation") # 点击会间接触发导航
# 导航完成后此处才继续

注意:使用 History API 改变 URL 也被视为一次导航;纯 hash 变化的导航会以 null 结果 resolve。其等待核心是 frame.ts 中基于 Waiter_setupNavigationWaiter——它会监听页面关闭、崩溃与“导航所在 frame 被分离”等拒绝条件。

waitForSelector / waitForFunction / waitForTimeout

  • [method: Frame.waitForSelector]——等待选择器满足 state 选项(attached/detached/visible/hidden)。若方法被调用时已满足条件立即返回;等待 hidden/detached 时返回 null;超过 timeout 抛错。该方法跨导航有效,典型用法是连续页面中循环等待首张图片:
for (const currentURL of ['https://google.com', 'https://bbc.com']) {
  await page.goto(currentURL);
  const element = await page.mainFrame().waitForSelector('img');
  console.log('Loaded image: ' + await element.getAttribute('src'));
}

官方提示:Playwright 动作前会自动等待元素就绪,使用 Locator 与 web-first 断言可以让代码完全摆脱 waitForSelector。此外,该方法不接受 Puppeteer 风格的 visibility/waitFor 参数——传了会直接抛错提示改用 state(见 frame.ts)。

  • [method: Frame.waitForFunction]——在表达式返回真值时立即 resolve,并返回该值。观察视口变化的经典示例:
const watchDog = page.mainFrame().waitForFunction('window.innerWidth < 100');
await page.setViewportSize({ width: 50, height: 50 });
await watchDog;

向谓词传参(如轮询检测某选择器出现):

const selector = '.foo';
await frame.waitForFunction(selector => !!document.querySelector(selector), selector);

polling 选项可传数字毫秒或字符串 'raf'——实现中 frame.ts 会拒绝 'raf' 以外的字符串轮询模式。

  • [method: Frame.waitForTimeout]——仅用于调试,生产测试使用固定时间等待本质上是 flaky 的,应改为等待网络事件、选择器可见等信号。

Frame 元信息与关系 API

方法 返回 说明
url() string 当前 frame 的 URL
name() string frame 的 name 属性;若为空则返回 id 属性。该值在 frame 创建时计算一次,之后属性变更不会更新
page() Page 包含该 frame 的页面对象
parentFrame() Frame | null 父框架;主框架与已分离框架返回 null
childFrames() Frame[] 子框架数组
isDetached() boolean 框架是否已被分离
frameElement() ElementHandle 对应此 frame 的 frame/iframe 元素句柄

frameElement() 是 [method: ElementHandle.contentFrame] 的逆运算——返回的句柄属于父 frame;若调用前 frame 已分离则抛错。可做一致性校验:

const frameElement = await frame.frameElement();
const contentFrame = await frameElement.contentFrame();
console.log(frame === contentFrame);  // true
frame_element = frame.frame_element()
content_frame = frame_element.content_frame()
assert frame == content_frame

源码视角:Frame 与 Page 的分工

从仓库源码可以确认几个关键实现事实,帮助你判断何时用 Page、何时直接拿 Frame

  1. Page 的大量方法只是主框架的薄封装:在 page.ts 中,page.$page.clickpage.fillpage.evaluatepage.locatorpage.goto 等均直接转发到 this._mainFrame 的对应方法(见 page.ts 区间的方法委托)。因此 Page 操作默认面向主框架;操作子框架内容必须显式取到对应 Frame

  2. 客户端以 ChannelOwner 模式双向同步框架状态Frame 继承自 ChannelOwner,构造时通过 initializer 还原 parentFramenameurlloadStates,并在构造体内订阅 loadstatenavigated 两个通道事件,用于推进 _loadStates、更新 URL、向上冒泡 FrameNavigated(见 frame.ts)。

  3. 超时分为“动作超时”与“导航超时”两套体系_timeout 读取 TimeoutSettings.timeout_navigationTimeout 读取 navigationTimeoutframe.ts),这解释了为何 gotowaitForNavigation 等导航类方法文档上标注的是 navigation-timeout 语义。

  4. 弃用倾向的代码证据Frame.setChecked 在客户端只是把 checked 布尔值路由到 check/uncheckframe.ts);大量方法类型注释与文档标记为 discouraged,指向同一能力集的 Locator 化实现。

选择器、选项与编程语言命名对照速查

本节汇总各 API 的公共参数与跨语言命名差异,便于快速对照查阅:

公共选项(出现在大部分交互/断言方法上):

选项 说明
selector 定位元素的引擎选择器
strict(v1.14+) 严格模式下选择器必须唯一命中,否则抛错
timeout 动作总超时(毫秒),0 禁用;JS 另有带 signal 的取消变体
force 跳过可操作性检查直接执行
noWaitAfter 动作后不等待可能触发的导航
trial(v1.11+) 只执行可操作性检查不真正操作
position 元素内点击/悬停的偏移坐标(相对元素左上角)
modifiers 按下期间的修饰键组合
signal(AbortSignal) JS 语言中用于取消正在进行的等待

语言命名对照:

语义 JS Python Java C#
框架 page.mainFrame() page.main_frame page.mainFrame() page.MainFrame
单元素求值 frame.$eval frame.eval_on_selector frame.evalOnSelector frame.EvalOnSelectorAsync
多元素求值 frame.$$eval frame.eval_on_selector_all frame.evalOnSelectorAll frame.EvalOnSelectorAllAsync
导航 frame.goto frame.goto frame.navigate frame.GotoAsync
等待导航 frame.waitForNavigation frame.expect_navigation() 回调式 RunAndWaitForNavigationAsync
元素文本类型 frame.type frame.type frame.type frame.TypeAsync

selectOption 的 Python 特有参数elementElementHandle)、indexvaluelabel,其余语言可用 SelectOption/SelectOptionValue 结构体按 labelvalueindex 匹配。

实操小结:五步把 iframe 场景跑起来

综合以上内容,一个典型的 iframe 测试可归纳为如下路径(以多语言能力通用):

  1. 找到目标 frame——拿到 Page 后要么用 page.frames() 遍历并按 url()/name() 匹配,要么直接针对已知的 iframe 用 frameLocator('#id')
  2. 进入定位——通过 FrameLocatorframe.locator() 建立 web-first 定位器,优先用 getByRole/getByText 等语义定位;
  3. 执行操作——用 locator.click()/fill() 等动作(或对旧脚本保留的 frame.click/frame.fill),依赖 Playwright 动作前自动等待;
  4. 求值与断言——frame.evaluate/evaluateHandle 处理页面内逻辑,web-first 断言检查最终状态;
  5. 等待与清理——需要精确同步时使用 waitForURL/waitForFunction/waitForLoadState;测试结束记得关闭浏览器并释放 JSHandle/ElementHandle

关联阅读:locators 文档actionability 可操作性说明、以及同一系列中 class-frame-locatorclass-page 等 API 参考;测试层面可查看 oopif.spec.ts(OOPIF 跨进程 iframe 覆盖)与 page-frame 相关测试 加深对框架树的验证方式的理解。

登录后查看全文
热门项目推荐
相关项目推荐