fix(fetch): JSON.stringify on client (#27644)
Fixes https://github.com/microsoft/playwright/issues/27602
This commit is contained in:
parent
e8b4c03e54
commit
4e845e7b1d
|
|
@ -168,13 +168,13 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
||||||
if (options.data !== undefined) {
|
if (options.data !== undefined) {
|
||||||
if (isString(options.data)) {
|
if (isString(options.data)) {
|
||||||
if (isJsonContentType(headers))
|
if (isJsonContentType(headers))
|
||||||
jsonData = options.data;
|
jsonData = isJsonParsable(options.data) ? options.data : JSON.stringify(options.data);
|
||||||
else
|
else
|
||||||
postDataBuffer = Buffer.from(options.data, 'utf8');
|
postDataBuffer = Buffer.from(options.data, 'utf8');
|
||||||
} else if (Buffer.isBuffer(options.data)) {
|
} else if (Buffer.isBuffer(options.data)) {
|
||||||
postDataBuffer = options.data;
|
postDataBuffer = options.data;
|
||||||
} else if (typeof options.data === 'object' || typeof options.data === 'number' || typeof options.data === 'boolean') {
|
} else if (typeof options.data === 'object' || typeof options.data === 'number' || typeof options.data === 'boolean') {
|
||||||
jsonData = options.data;
|
jsonData = JSON.stringify(options.data);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unexpected 'data' type`);
|
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 {
|
export class APIResponse implements api.APIResponse {
|
||||||
private readonly _initializer: channels.APIResponse;
|
private readonly _initializer: channels.APIResponse;
|
||||||
private readonly _headers: RawHeaders;
|
private readonly _headers: RawHeaders;
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ scheme.APIRequestContextFetchParams = tObject({
|
||||||
method: tOptional(tString),
|
method: tOptional(tString),
|
||||||
headers: tOptional(tArray(tType('NameValue'))),
|
headers: tOptional(tArray(tType('NameValue'))),
|
||||||
postData: tOptional(tBinary),
|
postData: tOptional(tBinary),
|
||||||
jsonData: tOptional(tAny),
|
jsonData: tOptional(tString),
|
||||||
formData: tOptional(tArray(tType('NameValue'))),
|
formData: tOptional(tArray(tType('NameValue'))),
|
||||||
multipartData: tOptional(tArray(tType('FormField'))),
|
multipartData: tOptional(tArray(tType('FormField'))),
|
||||||
timeout: tOptional(tNumber),
|
timeout: tOptional(tNumber),
|
||||||
|
|
|
||||||
|
|
@ -671,26 +671,11 @@ function parseCookie(header: string): channels.NetworkCookie | null {
|
||||||
return cookie;
|
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 {
|
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`);
|
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) {
|
if (params.jsonData !== undefined) {
|
||||||
const json = isJsonParsable(params.jsonData) ? params.jsonData : JSON.stringify(params.jsonData);
|
|
||||||
setHeader(headers, 'content-type', 'application/json', true);
|
setHeader(headers, 'content-type', 'application/json', true);
|
||||||
return Buffer.from(json, 'utf8');
|
return Buffer.from(params.jsonData, 'utf8');
|
||||||
} else if (params.formData) {
|
} else if (params.formData) {
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
for (const { name, value } of params.formData)
|
for (const { name, value } of params.formData)
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ export type APIRequestContextFetchParams = {
|
||||||
method?: string,
|
method?: string,
|
||||||
headers?: NameValue[],
|
headers?: NameValue[],
|
||||||
postData?: Binary,
|
postData?: Binary,
|
||||||
jsonData?: any,
|
jsonData?: string,
|
||||||
formData?: NameValue[],
|
formData?: NameValue[],
|
||||||
multipartData?: FormField[],
|
multipartData?: FormField[],
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
|
|
@ -330,7 +330,7 @@ export type APIRequestContextFetchOptions = {
|
||||||
method?: string,
|
method?: string,
|
||||||
headers?: NameValue[],
|
headers?: NameValue[],
|
||||||
postData?: Binary,
|
postData?: Binary,
|
||||||
jsonData?: any,
|
jsonData?: string,
|
||||||
formData?: NameValue[],
|
formData?: NameValue[],
|
||||||
multipartData?: FormField[],
|
multipartData?: FormField[],
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,7 @@ APIRequestContext:
|
||||||
type: array?
|
type: array?
|
||||||
items: NameValue
|
items: NameValue
|
||||||
postData: binary?
|
postData: binary?
|
||||||
jsonData: json?
|
jsonData: string?
|
||||||
formData:
|
formData:
|
||||||
type: array?
|
type: array?
|
||||||
items: NameValue
|
items: NameValue
|
||||||
|
|
|
||||||
|
|
@ -430,3 +430,24 @@ it('should keep headers capitalization', async ({ playwright, server }) => {
|
||||||
expect(serverRequest.rawHeaders).toContain('vaLUE');
|
expect(serverRequest.rawHeaders).toContain('vaLUE');
|
||||||
await request.dispose();
|
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();
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue