Skip to content

Commit ebe76d3

Browse files
feat(webdriver): support fetchPostData (#14340)
1 parent 23c4807 commit ebe76d3

File tree

10 files changed

+165
-45
lines changed

10 files changed

+165
-45
lines changed

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/puppeteer-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@
149149
"license": "Apache-2.0",
150150
"dependencies": {
151151
"@puppeteer/browsers": "2.10.12",
152-
"chromium-bidi": "10.5.0",
152+
"chromium-bidi": "10.5.1",
153153
"debug": "^4.4.3",
154154
"devtools-protocol": "0.0.1508733",
155155
"typed-query-selector": "^2.12.0",

packages/puppeteer-core/src/bidi/Browser.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,20 +111,28 @@ export class BidiBrowser extends Browser {
111111
}) as [string, ...string[]],
112112
);
113113

114-
try {
115-
await session.send('network.addDataCollector', {
116-
dataTypes: [Bidi.Network.DataType.Response],
117-
// Buffer size of 20 MB is equivalent to the CDP:
118-
maxEncodedDataSize: 20 * 1000 * 1000, // 20 MB
119-
});
120-
} catch (err) {
121-
if (err instanceof ProtocolError) {
122-
// Ignore protocol errors, as the data collectors can be not implemented.
123-
debugError(err);
124-
} else {
125-
throw err;
126-
}
127-
}
114+
await Promise.all(
115+
[Bidi.Network.DataType.Request, Bidi.Network.DataType.Response].map(
116+
// Data collectors might be not implemented for specific data type, so create them
117+
// separately and ignore protocol errors.
118+
async dataType => {
119+
try {
120+
await session.send('network.addDataCollector', {
121+
dataTypes: [dataType],
122+
// Buffer size of 20 MB is equivalent to the CDP:
123+
maxEncodedDataSize: 20_000_000,
124+
});
125+
} catch (err) {
126+
if (err instanceof ProtocolError) {
127+
debugError(err);
128+
} else {
129+
throw err;
130+
}
131+
}
132+
},
133+
),
134+
);
135+
128136
const browser = new BidiBrowser(session.browser, opts);
129137
browser.#initialize();
130138
return browser;

packages/puppeteer-core/src/bidi/HTTPRequest.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,11 @@ export class BidiHTTPRequest extends HTTPRequest {
158158
}
159159

160160
override hasPostData(): boolean {
161-
if (!this.#frame.page().browser().cdpSupported) {
162-
throw new UnsupportedOperation();
163-
}
164161
return this.#request.hasPostData;
165162
}
166163

167164
override async fetchPostData(): Promise<string | undefined> {
168-
throw new UnsupportedOperation();
165+
return await this.#request.fetchPostData();
169166
}
170167

171168
get #hasInternalHeaderOverwrite(): boolean {

packages/puppeteer-core/src/bidi/core/Request.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import * as Bidi from 'webdriver-bidi-protocol';
88

9-
import {ProtocolError} from '../../common/Errors.js';
9+
import {ProtocolError, UnsupportedOperation} from '../../common/Errors.js';
1010
import {EventEmitter} from '../../common/EventEmitter.js';
1111
import {inertIfDisposed} from '../../util/decorators.js';
1212
import {DisposableStack, disposeSymbol} from '../../util/disposable.js';
@@ -37,6 +37,7 @@ export class Request extends EventEmitter<{
3737
}
3838

3939
#responseContentPromise: Promise<Uint8Array<ArrayBufferLike>> | null = null;
40+
#requestBodyPromise: Promise<string> | null = null;
4041
#error?: string;
4142
#redirect?: Request;
4243
#response?: Bidi.Network.ResponseData;
@@ -195,8 +196,7 @@ export class Request extends EventEmitter<{
195196
}
196197

197198
get hasPostData(): boolean {
198-
// @ts-expect-error non-standard attribute.
199-
return this.#event.request['goog:hasPostData'] ?? false;
199+
return (this.#event.request.bodySize ?? 0) > 0;
200200
}
201201

202202
async continueRequest({
@@ -237,6 +237,29 @@ export class Request extends EventEmitter<{
237237
});
238238
}
239239

240+
async fetchPostData(): Promise<string | undefined> {
241+
if (!this.hasPostData) {
242+
return undefined;
243+
}
244+
if (!this.#requestBodyPromise) {
245+
this.#requestBodyPromise = (async () => {
246+
const data = await this.#session.send('network.getData', {
247+
dataType: Bidi.Network.DataType.Request,
248+
request: this.id,
249+
});
250+
if (data.result.bytes.type === 'string') {
251+
return data.result.bytes.value;
252+
}
253+
254+
// TODO: support base64 response.
255+
throw new UnsupportedOperation(
256+
`Collected request body data of type ${data.result.bytes.type} is not supported`,
257+
);
258+
})();
259+
}
260+
return await this.#requestBodyPromise;
261+
}
262+
240263
async getResponseContent(): Promise<Uint8Array> {
241264
if (!this.#responseContentPromise) {
242265
this.#responseContentPromise = (async () => {
@@ -258,7 +281,7 @@ export class Request extends EventEmitter<{
258281
)
259282
) {
260283
throw new ProtocolError(
261-
'Could not load body for this request. This might happen if the request is a preflight request.',
284+
'Could not load response body for this request. This might happen if the request is a preflight request.',
262285
);
263286
}
264287

packages/puppeteer-core/src/cdp/HTTPResponse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export class CdpHTTPResponse extends HTTPResponse {
139139
'No resource with given identifier found'
140140
) {
141141
throw new ProtocolError(
142-
'Could not load body for this request. This might happen if the request is a preflight request.',
142+
'Could not load response body for this request. This might happen if the request is a preflight request.',
143143
);
144144
}
145145

packages/puppeteer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
"license": "Apache-2.0",
133133
"dependencies": {
134134
"@puppeteer/browsers": "2.10.12",
135-
"chromium-bidi": "10.5.0",
135+
"chromium-bidi": "10.5.1",
136136
"cosmiconfig": "^9.0.0",
137137
"devtools-protocol": "0.0.1508733",
138138
"puppeteer-core": "24.25.0",

test/TestExpectations.json

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -453,13 +453,6 @@
453453
"expectations": ["FAIL"],
454454
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
455455
},
456-
{
457-
"testIdPattern": "[network.spec] network Request.postData should work with blobs",
458-
"platforms": ["darwin", "linux", "win32"],
459-
"parameters": ["webDriverBiDi"],
460-
"expectations": ["FAIL"],
461-
"comment": "https://github.com/w3c/webdriver-bidi/issues/747"
462-
},
463456
{
464457
"testIdPattern": "[network.spec] network Request.resourceType *",
465458
"platforms": ["darwin", "linux", "win32"],
@@ -1285,6 +1278,20 @@
12851278
"expectations": ["FAIL", "PASS"],
12861279
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
12871280
},
1281+
{
1282+
"testIdPattern": "[network.spec] network Request.fetchPostData should work",
1283+
"platforms": ["darwin", "linux", "win32"],
1284+
"parameters": ["firefox", "webDriverBiDi"],
1285+
"expectations": ["FAIL"],
1286+
"comment": "network.getData(request) is not yet supported"
1287+
},
1288+
{
1289+
"testIdPattern": "[network.spec] network Request.fetchPostData should work with blobs",
1290+
"platforms": ["darwin", "linux", "win32"],
1291+
"parameters": ["firefox", "webDriverBiDi"],
1292+
"expectations": ["FAIL"],
1293+
"comment": "network.getData(request) is not yet supported"
1294+
},
12881295
{
12891296
"testIdPattern": "[network.spec] network Request.postData should be |undefined| when there is no post data",
12901297
"platforms": ["darwin", "linux", "win32"],
@@ -1299,6 +1306,13 @@
12991306
"expectations": ["FAIL"],
13001307
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
13011308
},
1309+
{
1310+
"testIdPattern": "[network.spec] network Request.postData should work with blobs",
1311+
"platforms": ["darwin", "linux", "win32"],
1312+
"parameters": ["firefox", "webDriverBiDi"],
1313+
"expectations": ["FAIL"],
1314+
"comment": "network.getData(request) is not yet supported"
1315+
},
13021316
{
13031317
"testIdPattern": "[network.spec] network Response.text should throw when requesting body of redirected response",
13041318
"platforms": ["darwin", "linux", "win32"],

test/src/cdp/network.spec.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google Inc.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import expect from 'expect';
8+
import type {HTTPRequest} from 'puppeteer-core/internal/api/HTTPRequest.js';
9+
10+
import {getTestState, setupTestBrowserHooks} from '../mocha-utils.js';
11+
import {isFavicon, waitEvent} from '../utils.js';
12+
13+
describe('network', function () {
14+
setupTestBrowserHooks();
15+
16+
describe('Request.postData', function () {
17+
it('should work', async () => {
18+
const {page, server} = await getTestState();
19+
20+
await page.goto(server.EMPTY_PAGE);
21+
server.setRoute('/post', (_req, res) => {
22+
return res.end();
23+
});
24+
25+
const [request] = await Promise.all([
26+
waitEvent<HTTPRequest>(page, 'request', r => {
27+
return !isFavicon(r);
28+
}),
29+
page.evaluate(() => {
30+
return fetch('./post', {
31+
method: 'POST',
32+
body: JSON.stringify({foo: 'bar'}),
33+
});
34+
}),
35+
]);
36+
37+
expect(request).toBeTruthy();
38+
expect(request.postData()).toBe('{"foo":"bar"}');
39+
});
40+
41+
it('should be |undefined| when there is no post data', async () => {
42+
const {page, server} = await getTestState();
43+
44+
const response = (await page.goto(server.EMPTY_PAGE))!;
45+
expect(response.request().postData()).toBe(undefined);
46+
});
47+
48+
it('should work with blobs', async () => {
49+
const {page, server} = await getTestState();
50+
51+
await page.goto(server.EMPTY_PAGE);
52+
server.setRoute('/post', (_req, res) => {
53+
return res.end();
54+
});
55+
56+
const [request] = await Promise.all([
57+
waitEvent<HTTPRequest>(page, 'request', r => {
58+
return !isFavicon(r);
59+
}),
60+
page.evaluate(() => {
61+
return fetch('./post', {
62+
method: 'POST',
63+
body: new Blob([JSON.stringify({foo: 'bar'})], {
64+
type: 'application/json',
65+
}),
66+
});
67+
}),
68+
]);
69+
70+
expect(request).toBeTruthy();
71+
expect(request.postData()).toBe(undefined);
72+
expect(request.hasPostData()).toBe(true);
73+
expect(await request.fetchPostData()).toBe('{"foo":"bar"}');
74+
});
75+
});
76+
});

test/src/network.spec.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ describe('network', function () {
241241
});
242242
});
243243

244-
describe('Request.postData', function () {
244+
describe('Request.fetchPostData', function () {
245245
it('should work', async () => {
246246
const {page, server} = await getTestState();
247247

@@ -263,14 +263,17 @@ describe('network', function () {
263263
]);
264264

265265
expect(request).toBeTruthy();
266-
expect(request.postData()).toBe('{"foo":"bar"}');
266+
expect(request.hasPostData()).toBeTruthy();
267+
expect(await request.fetchPostData()).toBe('{"foo":"bar"}');
267268
});
268269

269270
it('should be |undefined| when there is no post data', async () => {
270271
const {page, server} = await getTestState();
271272

272273
const response = (await page.goto(server.EMPTY_PAGE))!;
273-
expect(response.request().postData()).toBe(undefined);
274+
expect(response.request().hasPostData()).toBeFalsy();
275+
276+
expect(await response.request().fetchPostData()).toBe(undefined);
274277
});
275278

276279
it('should work with blobs', async () => {
@@ -296,7 +299,6 @@ describe('network', function () {
296299
]);
297300

298301
expect(request).toBeTruthy();
299-
expect(request.postData()).toBe(undefined);
300302
expect(request.hasPostData()).toBe(true);
301303
expect(await request.fetchPostData()).toBe('{"foo":"bar"}');
302304
});
@@ -450,7 +452,7 @@ describe('network', function () {
450452

451453
const response = await responsePromise;
452454
await expect(response.buffer()).rejects.toThrow(
453-
'Could not load body for this request. This might happen if the request is a preflight request.',
455+
'Could not load response body for this request. This might happen if the request is a preflight request.',
454456
);
455457
});
456458
});

0 commit comments

Comments
 (0)