fix(fetch): JSON.stringify on client (#27644)

Fixes https://github.com/microsoft/playwright/issues/27602
This commit is contained in:
Yury Semikhatsky 2023-10-16 16:33:49 -07:00 committed by GitHub
parent e8b4c03e54
commit 4e845e7b1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 42 additions and 22 deletions

View File

@ -168,13 +168,13 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
if (options.data !== undefined) {
if (isString(options.data)) {
if (isJsonContentType(headers))
jsonData = options.data;
jsonData = isJsonParsable(options.data) ? options.data : JSON.stringify(options.data);
else
postDataBuffer = Buffer.from(options.data, 'utf8');
} else if (Buffer.isBuffer(options.data)) {
postDataBuffer = options.data;
} else if (typeof options.data === 'object' || typeof options.data === 'number' || typeof options.data === 'boolean') {
jsonData = options.data;
jsonData = JSON.stringify(options.data);
} else {
throw new Error(`Unexpected 'data' type`);
}
@ -230,6 +230,20 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
}
}
function isJsonParsable(value: any) {
if (typeof value !== 'string')
return false;
try {
JSON.parse(value);
return true;
} catch (e) {
if (e instanceof SyntaxError)
return false;
else
throw e;
}
}
export class APIResponse implements api.APIResponse {
private readonly _initializer: channels.APIResponse;
private readonly _headers: RawHeaders;

View File

@ -175,7 +175,7 @@ scheme.APIRequestContextFetchParams = tObject({
method: tOptional(tString),
headers: tOptional(tArray(tType('NameValue'))),
postData: tOptional(tBinary),
jsonData: tOptional(tAny),
jsonData: tOptional(tString),
formData: tOptional(tArray(tType('NameValue'))),
multipartData: tOptional(tArray(tType('FormField'))),
timeout: tOptional(tNumber),

View File

@ -671,26 +671,11 @@ function parseCookie(header: string): channels.NetworkCookie | null {
return cookie;
}
function isJsonParsable(value: any) {
if (typeof value !== 'string')
return false;
try {
JSON.parse(value);
return true;
} catch (e) {
if (e instanceof SyntaxError)
return false;
else
throw e;
}
}
function serializePostData(params: channels.APIRequestContextFetchParams, headers: HeadersObject): Buffer | undefined {
assert((params.postData ? 1 : 0) + (params.jsonData ? 1 : 0) + (params.formData ? 1 : 0) + (params.multipartData ? 1 : 0) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
if (params.jsonData !== undefined) {
const json = isJsonParsable(params.jsonData) ? params.jsonData : JSON.stringify(params.jsonData);
setHeader(headers, 'content-type', 'application/json', true);
return Buffer.from(json, 'utf8');
return Buffer.from(params.jsonData, 'utf8');
} else if (params.formData) {
const searchParams = new URLSearchParams();
for (const { name, value } of params.formData)

View File

@ -317,7 +317,7 @@ export type APIRequestContextFetchParams = {
method?: string,
headers?: NameValue[],
postData?: Binary,
jsonData?: any,
jsonData?: string,
formData?: NameValue[],
multipartData?: FormField[],
timeout?: number,
@ -330,7 +330,7 @@ export type APIRequestContextFetchOptions = {
method?: string,
headers?: NameValue[],
postData?: Binary,
jsonData?: any,
jsonData?: string,
formData?: NameValue[],
multipartData?: FormField[],
timeout?: number,

View File

@ -288,7 +288,7 @@ APIRequestContext:
type: array?
items: NameValue
postData: binary?
jsonData: json?
jsonData: string?
formData:
type: array?
items: NameValue

View File

@ -430,3 +430,24 @@ it('should keep headers capitalization', async ({ playwright, server }) => {
expect(serverRequest.rawHeaders).toContain('vaLUE');
await request.dispose();
});
it('should serialize post data on the client', async ({ playwright, server }) => {
const request = await playwright.request.newContext();
const serverReq = server.waitForRequest('/empty.html');
let onStack: boolean = true;
const postReq = request.post(server.EMPTY_PAGE, {
data: {
toJSON() {
if (!onStack)
throw new Error('Should not be called on the server');
return { 'foo': 'bar' };
}
}
});
onStack = false;
await postReq;
const body = await (await serverReq).postBody;
expect(body.toString()).toBe('{"foo":"bar"}');
// expect(serverRequest.rawHeaders).toContain('vaLUE');
await request.dispose();
});