feat(trace viewer): add "Copy as Playwright Request" button (#33298)

This commit is contained in:
Simon Knott 2024-11-20 10:16:43 +01:00 committed by GitHub
parent f1ddd379f3
commit 1d3605d1fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 991 additions and 7 deletions

View File

@ -0,0 +1,302 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Language } from '@isomorphic/locatorGenerators';
import type * as har from '@trace/har';
interface APIRequestCodegen {
generatePlaywrightRequestCall(request: har.Request, body: string | undefined): string;
}
class JSCodeGen implements APIRequestCodegen {
generatePlaywrightRequestCall(request: har.Request, body: string | undefined): string {
let method = request.method.toLowerCase();
const url = new URL(request.url);
const urlParam = `${url.origin}${url.pathname}`;
const options: any = {};
if (!['delete', 'get', 'head', 'post', 'put', 'patch'].includes(method)) {
options.method = method;
method = 'fetch';
}
if (url.searchParams.size)
options.params = Object.fromEntries(url.searchParams.entries());
if (body)
options.data = body;
if (request.headers.length)
options.headers = Object.fromEntries(request.headers.map(header => [header.name, header.value]));
const params = [`'${urlParam}'`];
const hasOptions = Object.keys(options).length > 0;
if (hasOptions)
params.push(this.prettyPrintObject(options));
return `await page.request.${method}(${params.join(', ')});`;
}
private prettyPrintObject(obj: any, indent = 2, level = 0): string {
// Handle null and undefined
if (obj === null)
return 'null';
if (obj === undefined)
return 'undefined';
// Handle primitive types
if (typeof obj !== 'object') {
if (typeof obj === 'string')
return this.stringLiteral(obj);
return String(obj);
}
// Handle arrays
if (Array.isArray(obj)) {
if (obj.length === 0)
return '[]';
const spaces = ' '.repeat(level * indent);
const nextSpaces = ' '.repeat((level + 1) * indent);
const items = obj.map(item =>
`${nextSpaces}${this.prettyPrintObject(item, indent, level + 1)}`
).join(',\n');
return `[\n${items}\n${spaces}]`;
}
// Handle regular objects
if (Object.keys(obj).length === 0)
return '{}';
const spaces = ' '.repeat(level * indent);
const nextSpaces = ' '.repeat((level + 1) * indent);
const entries = Object.entries(obj).map(([key, value]) => {
const formattedValue = this.prettyPrintObject(value, indent, level + 1);
// Handle keys that need quotes
const formattedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ?
key :
this.stringLiteral(key);
return `${nextSpaces}${formattedKey}: ${formattedValue}`;
}).join(',\n');
return `{\n${entries}\n${spaces}}`;
}
private stringLiteral(v: string): string {
v = v.replace(/\\/g, '\\\\').replace(/'/g, '\\\'');
if (v.includes('\n') || v.includes('\r') || v.includes('\t'))
return '`' + v + '`';
return `'${v}'`;
}
}
class PythonCodeGen implements APIRequestCodegen {
generatePlaywrightRequestCall(request: har.Request, body: string | undefined): string {
const url = new URL(request.url);
const urlParam = `${url.origin}${url.pathname}`;
const params: string[] = [`"${urlParam}"`];
let method = request.method.toLowerCase();
if (!['delete', 'get', 'head', 'post', 'put', 'patch'].includes(method)) {
params.push(`method="${method}"`);
method = 'fetch';
}
if (url.searchParams.size)
params.push(`params=${this.prettyPrintObject(Object.fromEntries(url.searchParams.entries()))}`);
if (body)
params.push(`data=${this.prettyPrintObject(body)}`);
if (request.headers.length)
params.push(`headers=${this.prettyPrintObject(Object.fromEntries(request.headers.map(header => [header.name, header.value])))}`);
const paramsString = params.length === 1 ? params[0] : `\n${params.map(p => this.indent(p, 2)).join(',\n')}\n`;
return `await page.request.${method}(${paramsString})`;
}
private indent(v: string, level: number): string {
return v.split('\n').map(s => ' '.repeat(level) + s).join('\n');
}
private prettyPrintObject(obj: any, indent = 2, level = 0): string {
// Handle null and undefined
if (obj === null)
return 'None';
if (obj === undefined)
return 'None';
// Handle primitive types
if (typeof obj !== 'object') {
if (typeof obj === 'string')
return this.stringLiteral(obj);
if (typeof obj === 'boolean')
return obj ? 'True' : 'False';
return String(obj);
}
// Handle arrays
if (Array.isArray(obj)) {
if (obj.length === 0)
return '[]';
const spaces = ' '.repeat(level * indent);
const nextSpaces = ' '.repeat((level + 1) * indent);
const items = obj.map(item =>
`${nextSpaces}${this.prettyPrintObject(item, indent, level + 1)}`
).join(',\n');
return `[\n${items}\n${spaces}]`;
}
// Handle regular objects
if (Object.keys(obj).length === 0)
return '{}';
const spaces = ' '.repeat(level * indent);
const nextSpaces = ' '.repeat((level + 1) * indent);
const entries = Object.entries(obj).map(([key, value]) => {
const formattedValue = this.prettyPrintObject(value, indent, level + 1);
return `${nextSpaces}${this.stringLiteral(key)}: ${formattedValue}`;
}).join(',\n');
return `{\n${entries}\n${spaces}}`;
}
private stringLiteral(v: string): string {
return JSON.stringify(v);
}
}
class CSharpCodeGen implements APIRequestCodegen {
generatePlaywrightRequestCall(request: har.Request, body: string | undefined): string {
const url = new URL(request.url);
const urlParam = `${url.origin}${url.pathname}`;
const options: any = {};
const initLines: string[] = [];
let method = request.method.toLowerCase();
if (!['delete', 'get', 'head', 'post', 'put', 'patch'].includes(method)) {
options.Method = method;
method = 'fetch';
}
if (url.searchParams.size)
options.Params = Object.fromEntries(url.searchParams.entries());
if (body)
options.Data = body;
if (request.headers.length)
options.Headers = Object.fromEntries(request.headers.map(header => [header.name, header.value]));
const params = [`"${urlParam}"`];
const hasOptions = Object.keys(options).length > 0;
if (hasOptions)
params.push(this.prettyPrintObject(options));
return `${initLines.join('\n')}${initLines.length ? '\n' : ''}await request.${this.toFunctionName(method)}(${params.join(', ')});`;
}
private toFunctionName(method: string): string {
return method[0].toUpperCase() + method.slice(1) + 'Async';
}
private prettyPrintObject(obj: any, indent = 2, level = 0): string {
// Handle null and undefined
if (obj === null)
return 'null';
if (obj === undefined)
return 'null';
// Handle primitive types
if (typeof obj !== 'object') {
if (typeof obj === 'string')
return this.stringLiteral(obj);
if (typeof obj === 'boolean')
return obj ? 'true' : 'false';
return String(obj);
}
// Handle arrays
if (Array.isArray(obj)) {
if (obj.length === 0)
return 'new object[] {}';
const spaces = ' '.repeat(level * indent);
const nextSpaces = ' '.repeat((level + 1) * indent);
const items = obj.map(item =>
`${nextSpaces}${this.prettyPrintObject(item, indent, level + 1)}`
).join(',\n');
return `new object[] {\n${items}\n${spaces}}`;
}
// Handle regular objects
if (Object.keys(obj).length === 0)
return 'new {}';
const spaces = ' '.repeat(level * indent);
const nextSpaces = ' '.repeat((level + 1) * indent);
const entries = Object.entries(obj).map(([key, value]) => {
const formattedValue = this.prettyPrintObject(value, indent, level + 1);
const formattedKey = level === 0 ? key : `[${this.stringLiteral(key)}]`;
return `${nextSpaces}${formattedKey} = ${formattedValue}`;
}).join(',\n');
return `new() {\n${entries}\n${spaces}}`;
}
private stringLiteral(v: string): string {
return JSON.stringify(v);
}
}
class JavaCodeGen implements APIRequestCodegen {
generatePlaywrightRequestCall(request: har.Request, body: string | undefined): string {
const url = new URL(request.url);
const params = [`"${url.origin}${url.pathname}"`];
const options: string[] = [];
let method = request.method.toLowerCase();
if (!['delete', 'get', 'head', 'post', 'put', 'patch'].includes(method)) {
options.push(`setMethod("${method}")`);
method = 'fetch';
}
for (const [key, value] of url.searchParams)
options.push(`setQueryParam(${this.stringLiteral(key)}, ${this.stringLiteral(value)})`);
if (body)
options.push(`setData(${this.stringLiteral(body)})`);
for (const header of request.headers)
options.push(`setHeader(${this.stringLiteral(header.name)}, ${this.stringLiteral(header.value)})`);
if (options.length > 0)
params.push(`RequestOptions.create()\n .${options.join('\n .')}\n`);
return `request.${method}(${params.join(', ')});`;
}
private stringLiteral(v: string): string {
return JSON.stringify(v);
}
}
export function getAPIRequestCodeGen(language: Language): APIRequestCodegen {
if (language === 'javascript')
return new JSCodeGen();
if (language === 'python')
return new PythonCodeGen();
if (language === 'csharp')
return new CSharpCodeGen();
if (language === 'java')
return new JavaCodeGen();
throw new Error('Unsupported language: ' + language);
}

View File

@ -22,11 +22,14 @@ import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
import { ToolbarButton } from '@web/components/toolbarButton';
import { generateCurlCommand, generateFetchCall } from '../third_party/devtools';
import { CopyToClipboardTextButton } from './copyToClipboard';
import { getAPIRequestCodeGen } from './codegen';
import type { Language } from '@isomorphic/locatorGenerators';
export const NetworkResourceDetails: React.FunctionComponent<{
resource: ResourceSnapshot;
onClose: () => void;
}> = ({ resource, onClose }) => {
sdkLanguage: Language;
}> = ({ resource, onClose, sdkLanguage }) => {
const [selectedTab, setSelectedTab] = React.useState('request');
return <TabbedPane
@ -36,7 +39,7 @@ export const NetworkResourceDetails: React.FunctionComponent<{
{
id: 'request',
title: 'Request',
render: () => <RequestTab resource={resource}/>,
render: () => <RequestTab resource={resource} sdkLanguage={sdkLanguage} />,
},
{
id: 'response',
@ -55,7 +58,8 @@ export const NetworkResourceDetails: React.FunctionComponent<{
const RequestTab: React.FunctionComponent<{
resource: ResourceSnapshot;
}> = ({ resource }) => {
sdkLanguage: Language;
}> = ({ resource, sdkLanguage }) => {
const [requestBody, setRequestBody] = React.useState<{ text: string, mimeType?: string } | null>(null);
React.useEffect(() => {
@ -96,6 +100,7 @@ const RequestTab: React.FunctionComponent<{
<div className='network-request-details-copy'>
<CopyToClipboardTextButton description='Copy as cURL' value={() => generateCurlCommand(resource)} />
<CopyToClipboardTextButton description='Copy as Fetch' value={() => generateFetchCall(resource)} />
<CopyToClipboardTextButton description='Copy as Playwright' value={async () => getAPIRequestCodeGen(sdkLanguage).generatePlaywrightRequestCall(resource.request, requestBody?.text)} />
</div>
{requestBody && <div className='network-request-details-header'>Request Body</div>}

View File

@ -26,6 +26,7 @@ import { GridView, type RenderedGridCell } from '@web/components/gridView';
import { SplitView } from '@web/components/splitView';
import type { ContextEntry } from '../types/entries';
import { NetworkFilters, defaultFilterState, type FilterState, type ResourceType } from './networkFilters';
import type { Language } from '@isomorphic/locatorGenerators';
type NetworkTabModel = {
resources: Entry[],
@ -66,7 +67,8 @@ export const NetworkTab: React.FunctionComponent<{
boundaries: Boundaries,
networkModel: NetworkTabModel,
onEntryHovered?: (entry: Entry | undefined) => void,
}> = ({ boundaries, networkModel, onEntryHovered }) => {
sdkLanguage: Language,
}> = ({ boundaries, networkModel, onEntryHovered, sdkLanguage }) => {
const [sorting, setSorting] = React.useState<Sorting | undefined>(undefined);
const [selectedEntry, setSelectedEntry] = React.useState<RenderedEntry | undefined>(undefined);
const [filterState, setFilterState] = React.useState(defaultFilterState);
@ -115,7 +117,7 @@ export const NetworkTab: React.FunctionComponent<{
sidebarIsFirst={true}
orientation='horizontal'
settingName='networkResourceDetails'
main={<NetworkResourceDetails resource={selectedEntry.resource} onClose={() => setSelectedEntry(undefined)} />}
main={<NetworkResourceDetails resource={selectedEntry.resource} onClose={() => setSelectedEntry(undefined)} sdkLanguage={sdkLanguage} />}
sidebar={grid}
/>}
</>;

View File

@ -238,7 +238,7 @@ const PropertiesView: React.FunctionComponent<{
id: 'network',
title: 'Network',
count: networkModel.resources.length,
render: () => <NetworkTab boundaries={boundaries} networkModel={networkModel} />
render: () => <NetworkTab boundaries={boundaries} networkModel={networkModel} sdkLanguage={sdkLanguage} />
};
const tabs: TabbedPaneTabModel[] = [

View File

@ -231,7 +231,7 @@ export const Workbench: React.FunctionComponent<{
id: 'network',
title: 'Network',
count: networkModel.resources.length,
render: () => <NetworkTab boundaries={boundaries} networkModel={networkModel} onEntryHovered={setHighlightedEntry}/>
render: () => <NetworkTab boundaries={boundaries} networkModel={networkModel} onEntryHovered={setHighlightedEntry} sdkLanguage={model?.sdkLanguage ?? 'javascript'} />
};
const attachmentsTab: TabbedPaneTabModel = {
id: 'attachments',

View File

@ -0,0 +1,675 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { test, expect } from '@playwright/test';
import { getAPIRequestCodeGen } from '../../../packages/trace-viewer/src/ui/codegen';
test.describe('javascript', () => {
const impl = getAPIRequestCodeGen('javascript');
test('generatePlaywrightRequestCall', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz',
method: 'GET',
headers: [{ name: 'User-Agent', value: 'Mozilla/5.0' }, { name: 'Date', value: '2021-01-01' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, 'foo')).toEqual(`
await page.request.get('http://example.com/foo', {
params: {
bar: 'baz'
},
data: 'foo',
headers: {
'User-Agent': 'Mozilla/5.0',
Date: '2021-01-01'
}
});`.trim());
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz',
method: 'OPTIONS',
headers: [],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.fetch('http://example.com/foo', {
method: 'options',
params: {
bar: 'baz'
}
});`.trim());
});
test('generatePlaywrightRequestCall with POST method and no body', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'POST',
headers: [{ name: 'Content-Type', value: 'application/json' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.post('http://example.com/foo', {
headers: {
'Content-Type': 'application/json'
}
});`.trim());
});
test('generatePlaywrightRequestCall with PUT method and JSON body', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'PUT',
headers: [{ name: 'Content-Type', value: 'application/json' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, '{"key":"value"}')).toEqual(`
await page.request.put('http://example.com/foo', {
data: '{"key":"value"}',
headers: {
'Content-Type': 'application/json'
}
});`.trim());
});
test('generatePlaywrightRequestCall with PATCH method and form data', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'PATCH',
headers: [{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, 'key=value')).toEqual(`
await page.request.patch('http://example.com/foo', {
data: 'key=value',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});`.trim());
});
test('generatePlaywrightRequestCall with DELETE method and custom header', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'DELETE',
headers: [{ name: 'Authorization', value: 'Bearer token' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.delete('http://example.com/foo', {
headers: {
Authorization: 'Bearer token'
}
});`.trim());
});
test('generatePlaywrightRequestCall with HEAD method', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'HEAD',
headers: [],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.head('http://example.com/foo');`.trim());
});
test('generatePlaywrightRequestCall with complex query parameters', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz&qux=quux',
method: 'GET',
headers: [],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.get('http://example.com/foo', {
params: {
bar: 'baz',
qux: 'quux'
}
});`.trim());
});
test('generatePlaywrightRequestCall with multiple headers', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'GET',
headers: [
{ name: 'User-Agent', value: 'Mozilla/5.0' },
{ name: 'Accept', value: 'application/json' },
{ name: 'Authorization', value: 'Bearer token' }
],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.get('http://example.com/foo', {
headers: {
'User-Agent': 'Mozilla/5.0',
Accept: 'application/json',
Authorization: 'Bearer token'
}
});`.trim());
});
test('escape sequences', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'GET',
headers: [
{ name: 'F\\o', value: 'B\\r' },
],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.get('http://example.com/foo', {
headers: {
'F\\\\o': 'B\\\\r'
}
});`.trim());
});
});
test.describe('python', () => {
const impl = getAPIRequestCodeGen('python');
test('generatePlaywrightRequestCall', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz',
method: 'GET',
headers: [{ name: 'User-Agent', value: 'Mozilla/5.0' }, { name: 'Date', value: '2021-01-01' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, 'foo')).toEqual(`
await page.request.get(
"http://example.com/foo",
params={
"bar": "baz"
},
data="foo",
headers={
"User-Agent": "Mozilla/5.0",
"Date": "2021-01-01"
}
)`.trim());
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz',
method: 'OPTIONS',
headers: [],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.fetch(
"http://example.com/foo",
method="options",
params={
"bar": "baz"
}
)`.trim());
});
test('generatePlaywrightRequestCall with POST method and no body', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'POST',
headers: [{ name: 'Content-Type', value: 'application/json' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.post(
"http://example.com/foo",
headers={
"Content-Type": "application/json"
}
)`.trim());
});
test('generatePlaywrightRequestCall with PUT method and JSON body', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'PUT',
headers: [{ name: 'Content-Type', value: 'application/json' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, '{"key":"value"}')).toEqual(`
await page.request.put(
"http://example.com/foo",
data="{\\"key\\":\\"value\\"}",
headers={
"Content-Type": "application/json"
}
)`.trim());
});
test('generatePlaywrightRequestCall with PATCH method and form data', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'PATCH',
headers: [{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, 'key=value')).toEqual(`
await page.request.patch(
"http://example.com/foo",
data="key=value",
headers={
"Content-Type": "application/x-www-form-urlencoded"
}
)`.trim());
});
test('generatePlaywrightRequestCall with DELETE method and custom header', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'DELETE',
headers: [{ name: 'Authorization', value: 'Bearer token' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.delete(
"http://example.com/foo",
headers={
"Authorization": "Bearer token"
}
)`.trim());
});
test('generatePlaywrightRequestCall with HEAD method', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'HEAD',
headers: [],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.head("http://example.com/foo")`.trim());
});
test('generatePlaywrightRequestCall with complex query parameters', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz&qux=quux',
method: 'GET',
headers: [],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.get(
"http://example.com/foo",
params={
"bar": "baz",
"qux": "quux"
}
)`.trim());
});
test('generatePlaywrightRequestCall with multiple headers', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'GET',
headers: [
{ name: 'User-Agent', value: 'Mozilla/5.0' },
{ name: 'Accept', value: 'application/json' },
{ name: 'Authorization', value: 'Bearer token' }
],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.get(
"http://example.com/foo",
headers={
"User-Agent": "Mozilla/5.0",
"Accept": "application/json",
"Authorization": "Bearer token"
}
)`.trim());
});
test('escape sequences', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'GET',
headers: [
{ name: 'F\\o', value: 'B\\r' },
],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await page.request.get(
"http://example.com/foo",
headers={
"F\\\\o": "B\\\\r"
}
)`.trim());
});
});
test.describe('csharp', () => {
const impl = getAPIRequestCodeGen('csharp');
test('generatePlaywrightRequestCall', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz',
method: 'GET',
headers: [{ name: 'User-Agent', value: 'Mozilla/5.0' }, { name: 'Date', value: '2021-01-01' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, 'foo')).toEqual(`
await request.GetAsync("http://example.com/foo", new() {
Params = new() {
["bar"] = "baz"
},
Data = "foo",
Headers = new() {
["User-Agent"] = "Mozilla/5.0",
["Date"] = "2021-01-01"
}
});`.trim());
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz',
method: 'OPTIONS',
headers: [],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await request.FetchAsync("http://example.com/foo", new() {
Method = "options",
Params = new() {
["bar"] = "baz"
}
});`.trim());
});
test('generatePlaywrightRequestCall with POST method and no body', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'POST',
headers: [{ name: 'Content-Type', value: 'application/json' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await request.PostAsync("http://example.com/foo", new() {
Headers = new() {
["Content-Type"] = "application/json"
}
});`.trim());
});
test('generatePlaywrightRequestCall with PUT method and JSON body', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'PUT',
headers: [{ name: 'Content-Type', value: 'application/json' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, '{"key":"value"}')).toEqual(`
await request.PutAsync("http://example.com/foo", new() {
Data = "{\\"key\\":\\"value\\"}",
Headers = new() {
["Content-Type"] = "application/json"
}
});`.trim());
});
test('escape sequences', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'GET',
headers: [
{ name: 'F\\o', value: 'B\\r' },
],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
await request.GetAsync("http://example.com/foo", new() {
Headers = new() {
["F\\\\o"] = "B\\\\r"
}
});`.trim());
});
});
test.describe('java', () => {
const impl = getAPIRequestCodeGen('java');
test('generatePlaywrightRequestCall', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz',
method: 'GET',
headers: [{ name: 'User-Agent', value: 'Mozilla/5.0' }, { name: 'Date', value: '2021-01-01' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, 'foo')).toEqual(`
request.get("http://example.com/foo", RequestOptions.create()
.setQueryParam("bar", "baz")
.setData("foo")
.setHeader("User-Agent", "Mozilla/5.0")
.setHeader("Date", "2021-01-01")
);`.trim());
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo?bar=baz',
method: 'OPTIONS',
headers: [],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
request.fetch("http://example.com/foo", RequestOptions.create()
.setMethod("options")
.setQueryParam("bar", "baz")
);`.trim());
});
test('generatePlaywrightRequestCall with POST method and no body', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'POST',
headers: [{ name: 'Content-Type', value: 'application/json' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
request.post("http://example.com/foo", RequestOptions.create()
.setHeader("Content-Type", "application/json")
);`.trim());
});
test('generatePlaywrightRequestCall with PUT method and JSON body', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'PUT',
headers: [{ name: 'Content-Type', value: 'application/json' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, '{"key":"value"}')).toEqual(`
request.put("http://example.com/foo", RequestOptions.create()
.setData("{\\"key\\":\\"value\\"}")
.setHeader("Content-Type", "application/json")
);`.trim());
});
test('generatePlaywrightRequestCall with PATCH method and form data', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'PATCH',
headers: [{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, 'key=value')).toEqual(`
request.patch("http://example.com/foo", RequestOptions.create()
.setData("key=value")
.setHeader("Content-Type", "application/x-www-form-urlencoded")
);`.trim());
});
test('generatePlaywrightRequestCall with DELETE method and custom header', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'DELETE',
headers: [{ name: 'Authorization', value: 'Bearer token' }],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
request.delete("http://example.com/foo", RequestOptions.create()
.setHeader("Authorization", "Bearer token")
);`.trim());
});
test('escape sequences', () => {
expect(impl.generatePlaywrightRequestCall({
url: 'http://example.com/foo',
method: 'GET',
headers: [
{ name: 'F\\o', value: 'B\\r' },
],
httpVersion: '1.1',
cookies: [],
queryString: [],
headersSize: 0,
bodySize: 0,
comment: '',
}, undefined)).toEqual(`
request.get(\"http://example.com/foo\", RequestOptions.create()
.setHeader(\"F\\\\o\", \"B\\\\r\")
);`.trim());
});
});