From 833cf2dc1cfcbad3da40a1f321fb33dd55452977 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 4 Nov 2024 09:06:41 +0100 Subject: [PATCH] add python --- packages/trace-viewer/src/ui/codegen.ts | 75 +++++++++ tests/library/unit/codegen.spec.ts | 193 ++++++++++++++++++++++++ 2 files changed, 268 insertions(+) diff --git a/packages/trace-viewer/src/ui/codegen.ts b/packages/trace-viewer/src/ui/codegen.ts index 64c52f949e..17492d2087 100644 --- a/packages/trace-viewer/src/ui/codegen.ts +++ b/packages/trace-viewer/src/ui/codegen.ts @@ -92,8 +92,83 @@ class JSCodeGen implements APIRequestCodegen { } } +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 `"${obj.replaceAll('"', '\\"')}"`; + 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}"${key}": ${formattedValue}`; + }).join(',\n'); + + return `{\n${entries}\n${spaces}}`; + } +} + export function getAPIRequestCodeGen(language: Language): APIRequestCodegen { if (language === 'javascript') return new JSCodeGen(); + if (language === 'python') + return new PythonCodeGen(); throw new Error('Unsupported language: ' + language); } diff --git a/tests/library/unit/codegen.spec.ts b/tests/library/unit/codegen.spec.ts index 1dd7c3b85a..6680de8796 100644 --- a/tests/library/unit/codegen.spec.ts +++ b/tests/library/unit/codegen.spec.ts @@ -202,3 +202,196 @@ await page.request.get('http://example.com/foo', { }); }); + +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()); + }); + +});