Skip to content

Commit

Permalink
fix(codegen): handle more context options (#15319)
Browse files Browse the repository at this point in the history
The following options now work across languages:
- `recordHar`
- `serviceWorkers`

In addition, object properties are now sorted alphabetically.
Drive-by: fixed `--target` help message to include all available targets.
  • Loading branch information
dgozman authored Jul 5, 2022
1 parent 5f03bd9 commit d60b8ab
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 42 deletions.
2 changes: 1 addition & 1 deletion packages/playwright-core/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Examples:
commandWithOpenOptions('codegen [url]', 'open page and generate code for user actions',
[
['-o, --output <file name>', 'saves the generated script to a file'],
['--target <language>', `language to generate, one of javascript, test, python, python-async, csharp`, language()],
['--target <language>', `language to generate, one of javascript, test, python, python-async, pytest, csharp, java`, language()],
]).action(function(url, options) {
codegen(options, url, options.target, options.output).catch(logErrorAndExit);
}).addHelpText('afterAll', `
Expand Down
24 changes: 20 additions & 4 deletions packages/playwright-core/src/server/recorder/csharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,14 @@ export class CSharpLanguageGenerator implements LanguageGenerator {

function formatObject(value: any, indent = ' ', name = ''): string {
if (typeof value === 'string') {
if (['permissions', 'colorScheme', 'modifiers', 'button'].includes(name))
if (['permissions', 'colorScheme', 'modifiers', 'button', 'recordHarContent', 'recordHarMode', 'serviceWorkers'].includes(name))
return `${getClassName(name)}.${toPascal(value)}`;
return quote(value);
}
if (Array.isArray(value))
return `new[] { ${value.map(o => formatObject(o, indent, name)).join(', ')} }`;
if (typeof value === 'object') {
const keys = Object.keys(value);
const keys = Object.keys(value).filter(key => value[key] !== undefined).sort();
if (!keys.length)
return name ? `new ${getClassName(name)}` : '';
const tokens: string[] = [];
Expand All @@ -193,6 +193,9 @@ function getClassName(value: string): string {
case 'permissions': return 'ContextPermission';
case 'modifiers': return 'KeyboardModifier';
case 'button': return 'MouseButton';
case 'recordHarMode': return 'HarMode';
case 'recordHarContent': return 'HarContentPolicy';
case 'serviceWorkers': return 'ServiceWorkerPolicy';
default: return toPascal(value);
}
}
Expand All @@ -209,19 +212,32 @@ function toPascal(value: string): string {
return value[0].toUpperCase() + value.slice(1);
}

function convertContextOptions(options: BrowserContextOptions): any {
const result: any = { ...options };
if (options.recordHar) {
result['recordHarPath'] = options.recordHar.path;
result['recordHarContent'] = options.recordHar.content;
result['recordHarMode'] = options.recordHar.mode;
result['recordHarOmitContent'] = options.recordHar.omitContent;
result['recordHarUrlFilter'] = options.recordHar.urlFilter;
delete result.recordHar;
}
return result;
}

function formatContextOptions(options: BrowserContextOptions, deviceName: string | undefined): string {
const device = deviceName && deviceDescriptors[deviceName];
if (!device) {
if (!Object.entries(options).length)
return '';
return formatObject(options, ' ', 'BrowserNewContextOptions');
return formatObject(convertContextOptions(options), ' ', 'BrowserNewContextOptions');
}

options = sanitizeDeviceOptions(device, options);
if (!Object.entries(options).length)
return `playwright.Devices[${quote(deviceName!)}]`;

return formatObject(options, ' ', `BrowserNewContextOptions(playwright.Devices[${quote(deviceName!)}])`);
return formatObject(convertContextOptions(options), ' ', `BrowserNewContextOptions(playwright.Devices[${quote(deviceName!)}])`);
}

class CSharpFormatter {
Expand Down
18 changes: 15 additions & 3 deletions packages/playwright-core/src/server/recorder/java.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,13 @@ function formatSelectOption(options: string | string[]): string {

function formatLaunchOptions(options: any): string {
const lines = [];
if (!Object.keys(options).length)
if (!Object.keys(options).filter(key => options[key] !== undefined).length)
return '';
lines.push('new BrowserType.LaunchOptions()');
if (typeof options.headless === 'boolean')
lines.push(` .setHeadless(false)`);
if (options.channel)
lines.push(` .setChannel(${quote(options.channel)})`);
if (typeof options.headless === 'boolean')
lines.push(` .setHeadless(false)`);
return lines.join('\n');
}

Expand Down Expand Up @@ -210,6 +210,18 @@ function formatContextOptions(contextOptions: BrowserContextOptions, deviceName:
lines.push(` .setLocale(${quote(options.locale)})`);
if (options.proxy)
lines.push(` .setProxy(new Proxy(${quote(options.proxy.server)}))`);
if (options.recordHar?.content)
lines.push(` .setRecordHarContent(HarContentPolicy.${options.recordHar?.content.toUpperCase()})`);
if (options.recordHar?.mode)
lines.push(` .setRecordHarMode(HarMode.${options.recordHar?.mode.toUpperCase()})`);
if (options.recordHar?.omitContent)
lines.push(` .setRecordHarOmitContent(true)`);
if (options.recordHar?.path)
lines.push(` .setRecordHarPath(Paths.get(${quote(options.recordHar.path)}))`);
if (options.recordHar?.urlFilter)
lines.push(` .setRecordHarUrlFilter(${quote(options.recordHar.urlFilter as string)})`);
if (options.serviceWorkers)
lines.push(` .setServiceWorkers(ServiceWorkerPolicy.${options.serviceWorkers.toUpperCase()})`);
if (options.storageState)
lines.push(` .setStorageStatePath(Paths.get(${quote(options.storageState as string)}))`);
if (options.timezoneId)
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/recorder/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ function formatObject(value: any, indent = ' '): string {
if (Array.isArray(value))
return `[${value.map(o => formatObject(o)).join(', ')}]`;
if (typeof value === 'object') {
const keys = Object.keys(value);
const keys = Object.keys(value).filter(key => value[key] !== undefined).sort();
if (!keys.length)
return '{}';
const tokens: string[] = [];
Expand Down
19 changes: 16 additions & 3 deletions packages/playwright-core/src/server/recorder/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ function toSnakeCase(name: string): string {
}

function formatOptions(value: any, hasArguments: boolean, asDict?: boolean): string {
const keys = Object.keys(value);
const keys = Object.keys(value).filter(key => value[key] !== undefined).sort();
if (!keys.length)
return '';
return (hasArguments ? ', ' : '') + keys.map(key => {
Expand All @@ -250,11 +250,24 @@ function formatOptions(value: any, hasArguments: boolean, asDict?: boolean): str
}).join(', ');
}

function convertContextOptions(options: BrowserContextOptions): any {
const result: any = { ...options };
if (options.recordHar) {
result['record_har_path'] = options.recordHar.path;
result['record_har_content'] = options.recordHar.content;
result['record_har_mode'] = options.recordHar.mode;
result['record_har_omit_content'] = options.recordHar.omitContent;
result['record_har_url_filter'] = options.recordHar.urlFilter;
delete result.recordHar;
}
return result;
}

function formatContextOptions(options: BrowserContextOptions, deviceName: string | undefined, asDict?: boolean): string {
const device = deviceName && deviceDescriptors[deviceName];
if (!device)
return formatOptions(options, false, asDict);
return `**playwright.devices[${quote(deviceName!)}]` + formatOptions(sanitizeDeviceOptions(device, options), true, asDict);
return formatOptions(convertContextOptions(options), false, asDict);
return `**playwright.devices[${quote(deviceName!)}]` + formatOptions(convertContextOptions(sanitizeDeviceOptions(device, options)), true, asDict);
}

class PythonFormatter {
Expand Down
8 changes: 0 additions & 8 deletions tests/library/inspector/cli-codegen-2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,14 +544,6 @@ test.describe('cli codegen', () => {
expect(fs.existsSync(traceFileName)).toBeTruthy();
});

test('should --save-har', async ({ runCLI }, testInfo) => {
const harFileName = testInfo.outputPath('har.har');
const cli = runCLI([`--save-har=${harFileName}`]);
await cli.exited;
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});

test('should fill tricky characters', async ({ page, openRecorder }) => {
const recorder = await openRecorder();

Expand Down
51 changes: 34 additions & 17 deletions tests/library/inspector/cli-codegen-csharp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { test, expect } from './inspectorTest';

const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => {
return channel ? `Headless = false,\n Channel = "${channel}",` : `Headless = false,`;
return channel ? `Channel = "${channel}",\n Headless = false,` : `Headless = false,`;
};

function capitalize(browserName: string): string {
Expand Down Expand Up @@ -70,21 +70,21 @@ test('should print the correct context options for custom settings', async ({ br
});
var context = await browser.NewContextAsync(new BrowserNewContextOptions
{
ViewportSize = new ViewportSize
{
Width = 1280,
Height = 720,
},
ColorScheme = ColorScheme.Dark,
Geolocation = new Geolocation
{
Latitude = 37.819722m,
Longitude = -122.478611m,
},
Permissions = new[] { ContextPermission.Geolocation },
UserAgent = "hardkodemium",
Locale = "es",
ColorScheme = ColorScheme.Dark,
Permissions = new[] { ContextPermission.Geolocation },
TimezoneId = "Europe/Rome",
UserAgent = "hardkodemium",
ViewportSize = new ViewportSize
{
Height = 720,
Width = 1280,
},
});`;
await cli.waitFor(expectedResult);
expect(cli.text()).toContain(expectedResult);
Expand Down Expand Up @@ -131,21 +131,21 @@ test('should print the correct context options when using a device and additiona
});
var context = await browser.NewContextAsync(new BrowserNewContextOptions(playwright.Devices["iPhone 11"])
{
UserAgent = "hardkodemium",
ViewportSize = new ViewportSize
{
Width = 1280,
Height = 720,
},
ColorScheme = ColorScheme.Dark,
Geolocation = new Geolocation
{
Latitude = 37.819722m,
Longitude = -122.478611m,
},
Permissions = new[] { ContextPermission.Geolocation },
Locale = "es",
ColorScheme = ColorScheme.Dark,
Permissions = new[] { ContextPermission.Geolocation },
TimezoneId = "Europe/Rome",
UserAgent = "hardkodemium",
ViewportSize = new ViewportSize
{
Height = 720,
Width = 1280,
},
});`;

await cli.waitFor(expectedResult);
Expand Down Expand Up @@ -176,3 +176,20 @@ test('should print load/save storageState', async ({ browserName, channel, runCL
`;
await cli.waitFor(expectedResult2);
});

test('should work with --save-har', async ({ runCLI }, testInfo) => {
const harFileName = testInfo.outputPath('har.har');
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`]);
const expectedResult = `
var context = await browser.NewContextAsync(new BrowserNewContextOptions
{
RecordHarMode = HarMode.Minimal,
RecordHarPath = ${JSON.stringify(harFileName)},
ServiceWorkers = ServiceWorkerPolicy.Block,
});`;
await cli.waitFor(expectedResult).catch(e => e);
expect(cli.text()).toContain(expectedResult);
await cli.exited;
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});
18 changes: 16 additions & 2 deletions tests/library/inspector/cli-codegen-java.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { test, expect } from './inspectorTest';

const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => {
return channel ? `.setHeadless(false)\n .setChannel("${channel}")` : '.setHeadless(false)';
return channel ? `.setChannel("${channel}")\n .setHeadless(false)` : '.setHeadless(false)';
};

test('should print the correct imports and context options', async ({ runCLI, channel, browserName }) => {
Expand Down Expand Up @@ -83,10 +83,24 @@ test('should print load/save storage_state', async ({ runCLI, browserName }, tes
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=java', emptyHTML]);
const expectedResult1 = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setStorageStatePath(Paths.get("${loadFileName.replace(/\\/g, '\\\\')}")));`;
.setStorageStatePath(Paths.get(${JSON.stringify(loadFileName)})));`;
await cli.waitFor(expectedResult1);

const expectedResult2 = `
context.storageState(new BrowserContext.StorageStateOptions().setPath("${saveFileName.replace(/\\/g, '\\\\')}"))`;
await cli.waitFor(expectedResult2);
});

test('should work with --save-har', async ({ runCLI }, testInfo) => {
const harFileName = testInfo.outputPath('har.har');
const cli = runCLI(['--target=java', `--save-har=${harFileName}`]);
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setRecordHarMode(HarMode.MINIMAL)
.setRecordHarPath(Paths.get(${JSON.stringify(harFileName)}))
.setServiceWorkers(ServiceWorkerPolicy.BLOCK));`;
await cli.waitFor(expectedResult).catch(e => e);
expect(cli.text()).toContain(expectedResult);
await cli.exited;
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});
2 changes: 1 addition & 1 deletion tests/library/inspector/cli-codegen-javascript.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { test, expect } from './inspectorTest';
const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'assets', 'empty.html')).toString();

const launchOptions = (channel: string) => {
return channel ? `headless: false,\n channel: '${channel}'` : 'headless: false';
return channel ? `channel: '${channel}',\n headless: false` : 'headless: false';
};

test('should print the correct imports and context options', async ({ browserName, channel, runCLI }) => {
Expand Down
13 changes: 12 additions & 1 deletion tests/library/inspector/cli-codegen-python-async.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { test, expect } from './inspectorTest';

const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => {
return channel ? `headless=False, channel="${channel}"` : 'headless=False';
return channel ? `channel="${channel}", headless=False` : 'headless=False';
};

test('should print the correct imports and context options', async ({ browserName, channel, runCLI }) => {
Expand Down Expand Up @@ -151,3 +151,14 @@ asyncio.run(main())
`;
await cli.waitFor(expectedResult2);
});

test('should work with --save-har', async ({ runCLI }, testInfo) => {
const harFileName = testInfo.outputPath('har.har');
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`]);
const expectedResult = `context = await browser.new_context(record_har_mode="minimal", record_har_path=${JSON.stringify(harFileName)}, service_workers="block")`;
await cli.waitFor(expectedResult).catch(e => e);
expect(cli.text()).toContain(expectedResult);
await cli.exited;
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});
2 changes: 1 addition & 1 deletion tests/library/inspector/cli-codegen-python.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { test, expect } from './inspectorTest';

const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => {
return channel ? `headless=False, channel="${channel}"` : 'headless=False';
return channel ? `channel="${channel}", headless=False` : 'headless=False';
};

test('should print the correct imports and context options', async ({ runCLI, channel, browserName }) => {
Expand Down
15 changes: 15 additions & 0 deletions tests/library/inspector/cli-codegen-test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,18 @@ test('test', async ({ page }) => {`;

await cli.waitFor(expectedResult);
});

test('should work with --save-har', async ({ runCLI }, testInfo) => {
const harFileName = testInfo.outputPath('har.har');
const cli = runCLI(['--target=test', `--save-har=${harFileName}`]);
const expectedResult = `
recordHar: {
mode: 'minimal',
path: '${harFileName.replace(/\\/g, '\\\\')}'
}`;
await cli.waitFor(expectedResult).catch(e => e);
expect(cli.text()).toContain(expectedResult);
await cli.exited;
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});

0 comments on commit d60b8ab

Please sign in to comment.