news 2026/9/7 8:26:33

Playwright Request 类全解:请求生命周期、API 方法与源码实现剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Playwright Request 类全解:请求生命周期、API 方法与源码实现剖析

Playwright Request 类全解:请求生命周期、API 方法与源码实现剖析

【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright

本文以 Playwright 官方 API 文档中的Request类为骨架,完整讲解页面发起每个网络请求时的事件序列(request/response/requestfinished/requestfailed)、Request类的全部成员方法(URL、方法、请求体、请求头、重定向链、资源类型、计时、体积等),并结合playwright-core中客户端与服务端两层实现源码,解释各方法的底层数据来源、headers()allHeaders()的差异、以及frame()为何可能在某些场景抛错。读完本文,你可以掌握在自动化与测试中做网络监听、失败请求审计、重定向链路追踪、性能计时采集和请求体解析的完整方案。

一、Request 在页面网络事件流中的位置

Request类自 v1.8 引入。当页面为某个网络资源发起请求时,Playwright 按如下顺序由 Page 发出事件:

  1. page.request—— 请求刚被页面发出时触发,此时拿到的就是Request对象;
  2. page.response—— 当(如果)收到该请求的响应状态码与响应头时触发;
  3. page.requestfinished—— 响应体下载完毕、请求完整结束时触发;
  4. page.requestfailed—— 请求在某一步失败时触发,替代requestfinished(也可能替代response)。

两条容易踩坑的边界规则(来自官方文档原注):

  • HTTP 错误码不算失败:404、503 这类 HTTP 错误响应从 HTTP 协议角度看仍是"成功收到响应",请求会以requestfinished收尾,而不会触发requestfailedrequestfailed只在网络层失败(DNS 解析失败、连接被重置、超时等)时触发,此时request.failure()会返回带errorText的对象;
  • 重定向会拆分请求:收到 3xx 响应时,当前请求以requestfinished正常结束,随后浏览器向重定向目标 URL 发出一个新的Request对象,新旧两个请求通过redirectedFrom()/redirectedTo()互相引用。

从源码看,事件序列在服务端 Request 类 中对应三条状态通道:_setFailureText()(L240-L243)写入失败文本并把等待响应的 Promise 以null解决;_setResponse()(L297-L301)挂上响应并 resolve 等待 Promise;构造器中redirectedFrom._redirectedTo = this(L209-L210)则完成了重定向链的双向链接。

二、基础信息读取:url、method、resourceType

最基础的三个方法是url()method()resourceType(),均为同步方法:

  • url()—— 请求的 URL。注意服务端构造时会先 stripFragmentFromUrl 去掉 URL 的#fragment部分,因此url()返回的 URL 不含锚点;
  • method()—— HTTP 方法(GET、POST 等);
  • resourceType()—— 渲染引擎视角的资源类型。

文档列出的resourceType()取值为:documentstylesheetimagemediafontscripttexttrackxhrfetcheventsourcewebsocketmanifestother。从 服务端 ResourceType 定义 看,内部类型联合比对外文档更宽,还包括beaconpingcspreport等,这些是浏览器的ResourceType枚举值,但 Playwright 对外的文档口径以文档列出的集合为准。

在 客户端 Request 类 中,这三者都直接读取初始化器_initializer中的字段(url()为 L127-L129,resourceType()为 L131-L133,method()为 L135-L137)。有一个值得注意的实现细节:url()method()会优先返回_fallbackOverrides中的覆盖值——这是route.continue({ url, method })之类的路由重写生效后,Request对象"看到"的是重写后的值,这也是文档中route.continue()改写 URL 后request.url()会变化这一行为的来源。

三、failure():请求失败信息

failure()在请求失败(requestfailed事件)时返回{ errorText }对象,其中errorText是人类可读的错误信息,例如'net::ERR_FAILED';请求未失败时返回null。JS 端返回{ errorText: string },Java / Python / C# 端则直接返回字符串(C# 为Failure属性)。

多语言用法(官方文档示例):

page.on('requestfailed', request => { console.log(request.url() + ' ' + request.failure().errorText); });
page.onRequestFailed(request -> { System.out.println(request.url() + " " + request.failure()); });
page.on("requestfailed", lambda request: print(request.url + " " + request.failure))
page.RequestFailed += (_, request) => { Console.WriteLine(request.Failure); };

实现上,客户端 failure() 只是把内部_failureText包装成{ errorText }返回;而_failureText的赋值发生在服务端 Request._setFailureText(),它同时把"等待响应"的 Promise 以null解决——这解释了为什么失败请求调用request.response()会拿到null而不是挂起:响应等待与失败事件在内部是同一条竞态通道。

四、frame()、serviceWorker() 与 isNavigationRequest():请求的"发起者"

frame()返回发起该请求的 Frame。文档明确提醒:部分场景下 frame 不可用,直接调用frame()会抛错,需要先用其他方法判别:

  • 请求源自 Service Worker —— 此时没有关联 Frame,用request.serviceWorker()检查;
  • 导航请求在对应 Frame 创建之前发出 —— 用request.isNavigationRequest()检查。

官方给出的全覆盖判断示例:

if (request.serviceWorker()) console.log(`request ${request.url()} from a service worker`); else if (request.isNavigationRequest()) console.log(`request ${request.url()} is a navigation request`); else console.log(`request ${request.url()} from a frame ${request.frame().url()}`);
String frameUrl = request.frame().url();
frame_url = request.frame.url
var frameUrl = request.Frame.Url;

抛错行为在 客户端 frame() 实现 中可以直接看到:_initializer.frame为空时断言当前请求必须有serviceWorker(),否则抛出Service Worker requests do not have an associated frame.;frame 存在但尚未挂到 Page(导航请求先于 Frame 创建)时,抛出包含"the request was issued before the frame is created"指引的错误信息,提示用户调用isNavigationRequest()判别。

serviceWorker()(since v1.24,JS/Python 可用)返回执行该请求的 Service Worker。注意该方法仅 Chromium 有值:在 Firefox / WebKit 上调用是安全的,但始终返回nullisNavigationRequest()表示该请求是否驱动 frame 导航;服务端通过_documentId是否存在来判定(Request.isNavigationRequest()),这与服务端按 document 维度跟踪导航的设计一致。

五、请求体:postData、postDataBuffer、postDataJSON

  • postData()—— 请求体字符串形式,无请求体时为null
  • postDataBuffer()—— 请求体二进制形式(Buffer),适合非文本负载;
  • postDataJSON()—— 结构化解析请求体。规则是:当Content-Typeapplication/x-www-form-urlencoded时返回键值对对象;否则按 JSON 解析(JS / Python 返回可序列化对象,C# 返回JsonElement)。

客户端实现印证了这一优先级:postDataJSON()先取postData()文本,命中form-urlencoded时用URLSearchParams逐对解析成对象;否则JSON.parse,解析失败会抛出带原始负载的POST data is not a valid JSON object错误。也就是说它并不是"先看请求头声明",而是先按 Content-Type 分岔、再以 JSON 兜底。

postData()postDataBuffer()同样会优先返回_fallbackOverrides.postDataBuffer,即被route.continue({ postData })重写后的请求体(客户端 _applyFallbackOverrides 会把字符串、Buffer、可序列化对象三种入参统一转成 Buffer 保存)。

六、请求头:headers()、allHeaders()、headersArray()、headerValue()

四个头相关方法各有分工,核心区别在于是否包含安全类头(如 cookie)以及是否保留头名原始大小写

方法返回特点
headers()Object<string, string>头名小写化;不含安全相关头(包括 cookie 类)
allHeaders()(v1.15+)Object<string, string>完整头(含cookie);头名小写化;异步
headersArray()(v1.15+)Array<{ name, value }>完整头;头名保留原始大小写;多值头(如Set-Cookie)会出现多条;异步
headerValue(name)(v1.15+)null \| string按名称(不区分大小写)取单个头的值;异步

实现层面,客户端 headers() 标注了@deprecated,直接读"临时头"_provisionalHeaders;而allHeaders()/headersArray()/headerValue()都经由 _actualHeaders() 走一条带 Promise 缓存的通道,向服务端请求rawRequestHeaders。对应地,服务端 Request 用ManualPromise保存原始头:setRawRequestHeaders()在真正拿到头数据时 resolve,若拿不到则回落到临时头。这也解释了为什么headers()是同步的(数据来自事件携带的快照),而allHeaders()等是异步的(可能要等浏览器补齐真实头)。

RawHeaders 是这一组方法的公共底座:内部用MultiMap按小写名索引;get()set-cookie这类多值头用换行拼接、其余用", "拼接;headersArray()则原样返回保留大小写与重复项的数组。

七、重定向链:redirectedFrom() 与 redirectedTo()

服务器返回 3xx 时,Playwright 会为新 URL 创建一个新Request,两者通过redirectedFrom()/redirectedTo()互链;多级重定向时可反复调用redirectedFrom()还原整条链路。

文档示例:若http://example.com重定向到https://example.com

const response = await page.goto('http://example.com'); console.log(response.request().redirectedFrom().url()); // 'http://example.com'
Response response = page.navigate("http://example.com"); System.out.println(response.request().redirectedFrom().url()); // "http://example.com"
response = await page.goto("http://example.com") print(response.request.redirected_from.url) # "http://example.com"
response = page.goto("http://example.com") print(response.request.redirected_from.url) # "http://example.com"
var response = await page.GotoAsync("http://www.microsoft.com"); Console.WriteLine(response.Request.RedirectedFrom?.Url); // http://www.microsoft.com

若没有发生重定向,redirectedFrom()返回null

const response = await page.goto('https://google.com'); console.log(response.request().redirectedFrom()); // null

redirectedTo()是其反向操作,文档给出的恒等式在所有语言下都成立:

console.log(request.redirectedFrom().redirectedTo() === request); // true
System.out.println(request.redirectedFrom().redirectedTo() == request); // true
assert request.redirected_from.redirected_to == request
Console.WriteLine(request.RedirectedFrom?.RedirectedTo == request); // True

源码中,双向链接在 客户端构造函数 建立:从initializer.redirectedFrom还原出前一个Request后,立即回写this._redirectedFrom._redirectedTo = this;服务端 Request 构造器 中同样有redirectedFrom._redirectedTo = this(L209-L210)。此外两个方向都提供_finalRequest()(客户端 L274-L276、服务端 L303-L305),沿_redirectedTo递归走到链尾,供内部(如路由与导航逻辑)取"最终请求"使用。测试tests/page/page-event-request.spec.tstests/page/page-network-request.spec.tstests/page/page-goto.spec.ts覆盖了重定向场景下事件序列与redirectedFrom断言,可作为行为验证参考。

八、响应关联:response() 与 existingResponse()

  • response()(异步)—— 等待并返回匹配的 Response;若因错误未收到响应则返回null。客户端实现 通过 channel 向服务端等待,对应服务端的_waitForResponsePromise,该 Promise 会在_setResponse()_setFailureText()时 resolve;
  • existingResponse()(since v1.59,同步)—— 只在响应已经到达时立即返回 Response,否则立即返回null,绝不等待。

existingResponse()解决的是事件回调中的时序问题:在request事件的回调里响应尚未到达,response()会挂起等待,而existingResponse()允许"现在就有就取、没有就跳过"的非阻塞模式。实现上它直接读取 客户端_response字段,该字段由 Response 构造函数 在响应对象创建时回填(this._request._response = this)。

九、timing():资源计时

timing()返回 Resource Timing 信息。多数计时值在收到响应后即可用,responseEnd要等请求完成(requestfinished)后才可用。返回对象字段(完整定义见 params.md 的 resource-timing 段落):

  • startTime:相对 1970-01-01 00:00:00 UTC 的毫秒数(与其他字段不同,它是绝对时间戳);
  • domainLookupStart/domainLookupEnd:DNS 查询前后;
  • connectStart/secureConnectionStart/connectEnd:建连与 TLS 握手(secureConnectionStart仅在安全连接时可用);
  • requestStart/responseStart:发出请求 / 收到首字节;
  • responseEnd:收到最后一字节。

startTime外的取值均为相对startTime的毫秒数,不可用则为-1。文档多语言示例:

const requestFinishedPromise = page.waitForEvent('requestfinished'); await page.goto('http://example.com'); const request = await requestFinishedPromise; console.log(request.timing());
page.onRequestFinished(request -> { Timing timing = request.timing(); System.out.println(timing.responseEnd - timing.startTime); }); page.navigate("http://example.com");
async with page.expect_event("requestfinished") as request_info: await page.goto("http://example.com") request = await request_info.value print(request.timing)
with page.expect_event("requestfinished") as request_info: page.goto("http://example.com") request = request_info.value print(request.timing)
var request = await page.RunAndWaitForRequestFinishedAsync(async () => { await page.GotoAsync("https://www.microsoft.com"); }); Console.WriteLine(request.Timing.ResponseEnd);

从源码看,客户端 timing() 只是返回内部_timing对象引用。该对象初始全为-1(构造函数 L114-L124),两处被填充:一是 Response 构造器 在响应到达时Object.assign(this._request._timing, this._initializer.timing),二是 _setResponseEndTiming() 在请求完成时写入responseEnd,并在responseStart缺失时以responseEnd兜底——这正是"多数值随响应用、responseEnd随请求完成"这一文档描述的代码依据,也意味着timing()返回的对象是持续变化的活引用,若在requestfinished前提前拷贝,拿到的可能仍是-1

十、sizes():请求与响应体积

sizes()(since v1.15,异步)返回四个字节数指标:

  • requestBodySize—— 请求体大小(POST 负载),无请求体时为 0;
  • requestHeadersSize—— 从 HTTP 请求消息起始到(含)body 前双 CRLF 的总字节数;
  • responseBodySize—— 已收到的响应体(编码后)大小;
  • responseHeadersSize—— 从 HTTP 响应消息起始到(含)body 前双 CRLF 的总字节数。

客户端 sizes() 的实现值得注意:它先await this.response(),若响应为null(请求失败)直接抛出Unable to fetch sizes for failed request,否则转调response._channel.sizes()。也就是说失败请求无法取体积,这是文档未强调但源码明确的约束。请求头大小在服务端由 _requestHeadersSize() 基于原始请求头计算。

十一、综合实战:一个可运行的请求审计脚本

把上述方法组合起来,可以得到一个覆盖"失败审计 + 重定向追踪 + 性能/体积采集"的完整示例(Node.js / JS 语法):

import { chromium } from 'playwright'; const browser = await chromium.launch(); const page = await browser.newPage(); // 1) 审计所有失败请求 page.on('requestfailed', request => { const failure = request.failure(); if (failure) console.log('FAILED', request.url(), failure.errorText); }); // 2) 追踪导航重定向链并采集最终请求数据 const finalRequestPromise = page.waitForEvent('requestfinished', request => request.resourceType() === 'document'); await page.goto('http://example.com', { waitUntil: 'domcontentloaded' }); const finalRequest = await finalRequestPromise; // 还原重定向链 let current = finalRequest; const chain = []; while (current) { chain.push(current.url()); current = current.redirectedFrom(); } console.log('redirect chain:', chain.reverse().join(' -> ')); // 3) 计时与体积 console.log('timing:', finalRequest.timing()); console.log('sizes:', await finalRequest.sizes()); console.log('method:', finalRequest.method()); console.log('resourceType:', finalRequest.resourceType()); // 完整请求头(含 cookie) console.log('all headers keys:', Object.keys(await finalRequest.allHeaders())); await browser.close();

对应的测试基建在本仓库中均有落点:事件序列与请求对象行为见tests/page/page-event-request.spec.tstests/page/page-network-request.spec.ts,体积采集见tests/page/page-network-sizes.spec.ts,路由改写(route.continueurl()/method()变化)见tests/page/page-request-continue.spec.ts

十二、与周边类的协作关系小结

  • 与 Route:page.route()的回调接收(route, request)route.continue({ url, method, headers, postData })会写入本文所述的_fallbackOverrides,使url()method()postData()headers()返回改写后的值;路由内部还维护"待处理 handler 链"(见 客户端 RouteHandler),一个 route 可被多个 handler 接力消费;
  • 与 Response:request.response()/response.request()双向可取;existingResponse()提供非阻塞查询;
  • 与 Worker:Service Worker 发起的请求用request.serviceWorker()定位发起者(仅 Chromium 有值),此时frame()不可用;
  • 与 HAR / Trace:网络请求事件同样驱动 HAR 录制与 Trace 的网络面板,相关行为可参考docs/src/network.mdtests/library/har.spec.ts

参考文件

  • 文档骨架:Request 类 API 文档
  • 资源计时字段定义:params.md - resource-timing
  • 客户端实现:client/network.ts - Request 类、RawHeaders
  • 服务端实现:server/network.ts - Request 类
  • 行为测试:page-event-request.spec.ts、page-network-request.spec.ts、page-network-sizes.spec.ts、page-request-continue.spec.ts

【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 8:25:31

java基础之运算符

一.运算符的定义在Java中&#xff0c;运算符是用于操作变量和值的符号&#xff0c;借助运算符能够对数据进行各类运算和操作。 二.运算符 1.算数运算符 算术运算符用于执行基本的数学运算&#xff0c;以下是常见的算术运算符及其示例&#xff1a; (1)加法运算符 整数相加 在对…

作者头像 李华
网站建设 2026/9/7 8:22:40

亡命迪斯科 MDO 自定义歌曲导入与打包全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 8:21:47

transformer用于图像分类

计算机视觉与transformer&#xff08;Vision transformer&#xff09;1、VIT:&#xff08;1&#xff09;原理&#xff1a;将图像分割成小块&#xff0c;通过线性变换得到patch embedding&#xff0c;加上位置编码&#xff0c;输入到encoder中&#xff0c;最后用分类头进行分类。…

作者头像 李华
网站建设 2026/9/7 8:21:43

IOPaint:AI图片修复的免费完整指南

IOPaint&#xff1a;AI图片修复的免费完整指南 【免费下载链接】IOPaint Image inpainting tool powered by SOTA AI Model. Remove any unwanted object, defect, people from your pictures or erase and replace(powered by stable diffusion) any thing on your pictures. …

作者头像 李华
网站建设 2026/9/7 8:19:29

双闭环直流调速系统从原理到SIMULINK仿真完整设计指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 8:17:40

T+转U8+工具全解析:历史数据迁移的实操指南与避坑经验

简介&#xff1a;面向用友T与U8数据迁移场景&#xff0c;提供从T账套转换至U8所需的核心程序与配套脚本。包内共13个文件&#xff0c;含8个SQL脚本、2个配置文件、2个OCX控件及1个主程序exe&#xff0c;压缩包仅426KB&#xff0c;轻量易部署。SQL脚本覆盖总账、应收、应付、存货…

作者头像 李华