refactor to support multiple languages
This commit is contained in:
parent
6ffab68dfa
commit
7ef5d8d5d8
|
|
@ -14,73 +14,86 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Language } from '@isomorphic/locatorGenerators';
|
||||
import type * as har from '@trace/har';
|
||||
|
||||
export function 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(prettyPrintObject(options));
|
||||
return `await page.request.${method}(${params.join(', ')});`;
|
||||
interface APIRequestCodegen {
|
||||
generatePlaywrightRequestCall(request: har.Request, body: string | undefined): string;
|
||||
}
|
||||
|
||||
function prettyPrintObject(obj: any, indent = 2, level = 0): string {
|
||||
// Handle null and undefined
|
||||
if (obj === null)
|
||||
return 'null';
|
||||
if (obj === undefined)
|
||||
return 'undefined';
|
||||
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]));
|
||||
|
||||
// Handle primitive types
|
||||
if (typeof obj !== 'object') {
|
||||
if (typeof obj === 'string')
|
||||
return `'${obj}'`;
|
||||
return String(obj);
|
||||
const params = [`'${urlParam}'`];
|
||||
const hasOptions = Object.keys(options).length > 0;
|
||||
if (hasOptions)
|
||||
params.push(this.prettyPrintObject(options));
|
||||
return `await page.request.${method}(${params.join(', ')});`;
|
||||
}
|
||||
|
||||
// Handle arrays
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0)
|
||||
return '[]';
|
||||
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 `'${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 items = obj.map(item =>
|
||||
`${nextSpaces}${prettyPrintObject(item, indent, level + 1)}`
|
||||
).join(',\n');
|
||||
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 :
|
||||
`'${key}'`;
|
||||
return `${nextSpaces}${formattedKey}: ${formattedValue}`;
|
||||
}).join(',\n');
|
||||
|
||||
return `[\n${items}\n${spaces}]`;
|
||||
return `{\n${entries}\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 = prettyPrintObject(value, indent, level + 1);
|
||||
// Handle keys that need quotes
|
||||
const formattedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ?
|
||||
key :
|
||||
`'${key}'`;
|
||||
return `${nextSpaces}${formattedKey}: ${formattedValue}`;
|
||||
}).join(',\n');
|
||||
|
||||
return `{\n${entries}\n${spaces}}`;
|
||||
}
|
||||
|
||||
export function getAPIRequestCodeGen(language: Language): APIRequestCodegen {
|
||||
if (language === 'javascript')
|
||||
return new JSCodeGen();
|
||||
throw new Error('Unsupported language: ' + language);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
|||
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||
import { generateCurlCommand, generateFetchCall } from '../third_party/devtools';
|
||||
import { CopyToClipboardTextButton } from './copyToClipboard';
|
||||
import { generatePlaywrightRequestCall } from '@isomorphic/codegen';
|
||||
import { getAPIRequestCodeGen } from './codegen';
|
||||
import type { Language } from '@isomorphic/locatorGenerators';
|
||||
|
||||
export const NetworkResourceDetails: React.FunctionComponent<{
|
||||
resource: ResourceSnapshot;
|
||||
onClose: () => void;
|
||||
sdkLanguage?: Language;
|
||||
sdkLanguage: Language;
|
||||
}> = ({ resource, onClose, sdkLanguage }) => {
|
||||
const [selectedTab, setSelectedTab] = React.useState('request');
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ export const NetworkResourceDetails: React.FunctionComponent<{
|
|||
|
||||
const RequestTab: React.FunctionComponent<{
|
||||
resource: ResourceSnapshot;
|
||||
sdkLanguage?: Language;
|
||||
sdkLanguage: Language;
|
||||
}> = ({ resource, sdkLanguage }) => {
|
||||
const [requestBody, setRequestBody] = React.useState<{ text: string, mimeType?: string } | null>(null);
|
||||
|
||||
|
|
@ -100,7 +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)} />
|
||||
{sdkLanguage === 'javascript' && <CopyToClipboardTextButton description='Copy as Playwright' value={async () => generatePlaywrightRequestCall(resource.request, requestBody?.text)} />}
|
||||
<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>}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export const NetworkTab: React.FunctionComponent<{
|
|||
boundaries: Boundaries,
|
||||
networkModel: NetworkTabModel,
|
||||
onEntryHovered?: (entry: Entry | undefined) => void,
|
||||
sdkLanguage?: Language,
|
||||
sdkLanguage: Language,
|
||||
}> = ({ boundaries, networkModel, onEntryHovered, sdkLanguage }) => {
|
||||
const [sorting, setSorting] = React.useState<Sorting | undefined>(undefined);
|
||||
const [selectedEntry, setSelectedEntry] = React.useState<RenderedEntry | undefined>(undefined);
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ export const Workbench: React.FunctionComponent<{
|
|||
id: 'network',
|
||||
title: 'Network',
|
||||
count: networkModel.resources.length,
|
||||
render: () => <NetworkTab boundaries={boundaries} networkModel={networkModel} onEntryHovered={setHighlightedEntry} sdkLanguage={model?.sdkLanguage} />
|
||||
render: () => <NetworkTab boundaries={boundaries} networkModel={networkModel} onEntryHovered={setHighlightedEntry} sdkLanguage={model?.sdkLanguage ?? 'javascript'} />
|
||||
};
|
||||
const attachmentsTab: TabbedPaneTabModel = {
|
||||
id: 'attachments',
|
||||
|
|
|
|||
|
|
@ -15,20 +15,24 @@
|
|||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { generatePlaywrightRequestCall } from '../../../packages/trace-viewer/src/ui/codegen';
|
||||
import { getAPIRequestCodeGen } from '../../../packages/trace-viewer/src/ui/codegen';
|
||||
|
||||
test('generatePlaywrightRequestCall', () => {
|
||||
expect(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(`
|
||||
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'
|
||||
|
|
@ -40,154 +44,154 @@ await page.request.get('http://example.com/foo', {
|
|||
}
|
||||
});`.trim());
|
||||
|
||||
expect(generatePlaywrightRequestCall({
|
||||
url: 'http://example.com/foo?bar=baz',
|
||||
method: 'OPTIONS',
|
||||
headers: [],
|
||||
httpVersion: '1.1',
|
||||
cookies: [],
|
||||
queryString: [],
|
||||
headersSize: 0,
|
||||
bodySize: 0,
|
||||
comment: '',
|
||||
}, undefined)).toEqual(`
|
||||
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(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(`
|
||||
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(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(`
|
||||
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(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(`
|
||||
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(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(`
|
||||
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(generatePlaywrightRequestCall({
|
||||
url: 'http://example.com/foo',
|
||||
method: 'HEAD',
|
||||
headers: [],
|
||||
httpVersion: '1.1',
|
||||
cookies: [],
|
||||
queryString: [],
|
||||
headersSize: 0,
|
||||
bodySize: 0,
|
||||
comment: '',
|
||||
}, undefined)).toEqual(`
|
||||
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(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(`
|
||||
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(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(`
|
||||
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',
|
||||
|
|
@ -195,4 +199,6 @@ await page.request.get('http://example.com/foo', {
|
|||
Authorization: 'Bearer token'
|
||||
}
|
||||
});`.trim());
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue