fix(codegen): update csharp scripts to new syntax (#6685)

Drive-by: fix middle/right button clicks in codegen.
This commit is contained in:
Dmitry Gozman 2021-05-20 15:47:14 -07:00 committed by GitHub
parent 08773e836b
commit e4946b79e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 258 additions and 234 deletions

View file

@ -142,6 +142,7 @@ export class Recorder {
removeEventListeners(this._listeners); removeEventListeners(this._listeners);
this._listeners = [ this._listeners = [
addEventListener(document, 'click', event => this._onClick(event as MouseEvent), true), addEventListener(document, 'click', event => this._onClick(event as MouseEvent), true),
addEventListener(document, 'auxclick', event => this._onClick(event as MouseEvent), true),
addEventListener(document, 'input', event => this._onInput(event), true), addEventListener(document, 'input', event => this._onInput(event), true),
addEventListener(document, 'keydown', event => this._onKeyDown(event as KeyboardEvent), true), addEventListener(document, 'keydown', event => this._onKeyDown(event as KeyboardEvent), true),
addEventListener(document, 'keyup', event => this._onKeyUp(event as KeyboardEvent), true), addEventListener(document, 'keyup', event => this._onKeyUp(event as KeyboardEvent), true),

View file

@ -28,7 +28,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
generateAction(actionInContext: ActionInContext): string { generateAction(actionInContext: ActionInContext): string {
const { action, pageAlias } = actionInContext; const { action, pageAlias } = actionInContext;
const formatter = new CSharpFormatter(0); const formatter = new CSharpFormatter(8);
formatter.newLine(); formatter.newLine();
formatter.add('// ' + actionTitle(action)); formatter.add('// ' + actionTitle(action));
@ -41,63 +41,62 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
const subject = actionInContext.isMainFrame ? pageAlias : const subject = actionInContext.isMainFrame ? pageAlias :
(actionInContext.frameName ? (actionInContext.frameName ?
`${pageAlias}.GetFrame(name: ${quote(actionInContext.frameName)})` : `${pageAlias}.Frame(${quote(actionInContext.frameName)})` :
`${pageAlias}.GetFrame(url: ${quote(actionInContext.frameUrl)})`); `${pageAlias}.FrameByUrl(${quote(actionInContext.frameUrl)})`);
const signals = toSignalMap(action); const signals = toSignalMap(action);
if (signals.dialog) { if (signals.dialog) {
formatter.add(` void ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler(object sender, DialogEventArgs e) formatter.add(` void ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler(object sender, IDialog dialog)
{ {
Console.WriteLine($"Dialog message: {e.Dialog.Message}"); Console.WriteLine($"Dialog message: {dialog.Message}");
e.Dialog.DismissAsync(); dialog.DismissAsync();
${pageAlias}.Dialog -= ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler; ${pageAlias}.Dialog -= ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler;
} }
${pageAlias}.Dialog += ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler;`); ${pageAlias}.Dialog += ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler;`);
} }
const emitTaskWhenAll = signals.waitForNavigation || signals.popup || signals.download; const lines: string[] = [];
if (emitTaskWhenAll) { const actionCall = this._generateActionCall(action, actionInContext.isMainFrame);
if (signals.popup) if (signals.waitForNavigation) {
formatter.add(`var ${signals.popup.popupAlias}Task = ${pageAlias}.WaitForEventAsync(PageEvent.Popup)`); lines.push(`await Task.WhenAll(`);
else if (signals.download) lines.push(`${pageAlias}.WaitForNavigationAsync(/* new ${actionInContext.isMainFrame ? 'Page' : 'Frame'}WaitForNavigationOptions`);
formatter.add(`var downloadTask = ${pageAlias}.WaitForEventAsync(PageEvent.Download);`); lines.push(`{`);
lines.push(` UrlString = ${quote(signals.waitForNavigation.url)}`);
formatter.add(`await Task.WhenAll(`); lines.push(`} */),`);
lines.push(`${subject}.${actionCall});`);
} else {
lines.push(`await ${subject}.${actionCall};`);
} }
// Popup signals. if (signals.download) {
if (signals.popup) lines.unshift(`var download${signals.download.downloadAlias} = await ${pageAlias}.RunAndWaitForEventAsync(PageEvent.Download, async () =>\n{`);
formatter.add(`${signals.popup.popupAlias}Task,`); lines.push(`});`);
}
// Navigation signal. if (signals.popup) {
if (signals.waitForNavigation) lines.unshift(`var ${signals.popup.popupAlias} = await ${pageAlias}.RunAndWaitForEventAsync(PageEvent.Popup, async () =>\n{`);
formatter.add(`${pageAlias}.WaitForNavigationAsync(/*${quote(signals.waitForNavigation.url)}*/),`); lines.push(`});`);
}
// Download signals. for (const line of lines)
if (signals.download) formatter.add(line);
formatter.add(`downloadTask,`);
const prefix = (signals.popup || signals.waitForNavigation || signals.download) ? '' : 'await ';
const actionCall = this._generateActionCall(action);
const suffix = emitTaskWhenAll ? ');' : ';';
formatter.add(`${prefix}${subject}.${actionCall}${suffix}`);
if (signals.assertNavigation) if (signals.assertNavigation)
formatter.add(` // Assert.Equal(${quote(signals.assertNavigation.url)}, ${pageAlias}.Url);`); formatter.add(` // Assert.Equal(${quote(signals.assertNavigation.url)}, ${pageAlias}.Url);`);
return formatter.format(); return formatter.format();
} }
private _generateActionCall(action: Action): string { private _generateActionCall(action: Action, isPage: boolean): string {
switch (action.name) { switch (action.name) {
case 'openPage': case 'openPage':
throw Error('Not reached'); throw Error('Not reached');
case 'closePage': case 'closePage':
return 'CloseAsync()'; return 'CloseAsync()';
case 'click': { case 'click': {
let method = 'ClickAsync'; let method = 'Click';
if (action.clickCount === 2) if (action.clickCount === 2)
method = 'DblClickAsync'; method = 'DblClick';
const modifiers = toModifiers(action.modifiers); const modifiers = toModifiers(action.modifiers);
const options: MouseClickOptions = {}; const options: MouseClickOptions = {};
if (action.button !== 'left') if (action.button !== 'left')
@ -106,8 +105,10 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
options.modifiers = modifiers; options.modifiers = modifiers;
if (action.clickCount > 2) if (action.clickCount > 2)
options.clickCount = action.clickCount; options.clickCount = action.clickCount;
const optionsString = formatOptions(options, true, false); if (!Object.entries(options).length)
return `${method}(${quote(action.selector)}${optionsString})`; return `${method}Async(${quote(action.selector)})`;
const optionsString = formatObject(options, ' ', (isPage ? 'Page' : 'Frame') + method + 'Options');
return `${method}Async(${quote(action.selector)}, ${optionsString})`;
} }
case 'check': case 'check':
return `CheckAsync(${quote(action.selector)})`; return `CheckAsync(${quote(action.selector)})`;
@ -116,7 +117,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
case 'fill': case 'fill':
return `FillAsync(${quote(action.selector)}, ${quote(action.text)})`; return `FillAsync(${quote(action.selector)}, ${quote(action.text)})`;
case 'setInputFiles': case 'setInputFiles':
return `SetInputFilesAsync(${quote(action.selector)}, ${formatObject(action.files.length === 1 ? action.files[0] : action.files)})`; return `SetInputFilesAsync(${quote(action.selector)}, ${formatObject(action.files)})`;
case 'press': { case 'press': {
const modifiers = toModifiers(action.modifiers); const modifiers = toModifiers(action.modifiers);
const shortcut = [...modifiers, action.key].join('+'); const shortcut = [...modifiers, action.key].join('+');
@ -125,68 +126,37 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
case 'navigate': case 'navigate':
return `GotoAsync(${quote(action.url)})`; return `GotoAsync(${quote(action.url)})`;
case 'select': case 'select':
return `SelectOptionAsync(${quote(action.selector)}, ${formatObject(action.options.length > 1 ? action.options : action.options[0])})`; return `SelectOptionAsync(${quote(action.selector)}, ${formatObject(action.options)})`;
} }
} }
generateHeader(options: LanguageGeneratorOptions): string { generateHeader(options: LanguageGeneratorOptions): string {
const formatter = new CSharpFormatter(0); const formatter = new CSharpFormatter(0);
formatter.add(` formatter.add(`
await Playwright.InstallAsync(); using System;
using var playwright = await Playwright.CreateAsync(); using System.Threading.Tasks;
await using var browser = await playwright.${toPascal(options.browserName)}.LaunchAsync(${formatArgs(options.launchOptions)} using Microsoft.Playwright;
);
var context = await browser.NewContextAsync(${formatContextOptions(options.contextOptions, options.deviceName)});`); class Example
{
static async Task Main(string[] args)
{
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${toPascal(options.browserName)}.LaunchAsync(${formatObject(options.launchOptions, ' ', 'BrowserTypeLaunchOptions')});
var context = await browser.NewContextAsync(${formatContextOptions(options.contextOptions, options.deviceName)});`);
return formatter.format(); return formatter.format();
} }
generateFooter(saveStorage: string | undefined): string { generateFooter(saveStorage: string | undefined): string {
const storageStateLine = saveStorage ? `\nawait context.StorageStateAsync(path: "${saveStorage}");` : ''; const storageStateLine = saveStorage ? `\n await context.StorageStateAsync(new BrowserContextStorageStateOptions\n {\n Path = ${quote(saveStorage)}\n });\n` : '';
return `\n// ---------------------${storageStateLine}`; return `${storageStateLine} }
}`;
} }
} }
function formatValue(value: any): string {
if (value === false)
return 'false';
if (value === true)
return 'true';
if (value === undefined)
return 'null';
if (Array.isArray(value))
return `new [] {${value.map(formatValue).join(', ')}}`;
if (typeof value === 'string')
return quote(value);
return String(value);
}
function formatOptions(value: any, hasArguments: boolean, isInitializing: boolean): string {
const keys = Object.keys(value);
if (!keys.length)
return '';
return (hasArguments ? ', ' : '') + keys.map(key => `${key}${isInitializing ? ': ' : ' = '}${formatValue(value[key])}`).join(', ');
}
function formatArgs(value: any, indent = ' '): string {
if (typeof value === 'string')
return quote(value);
if (Array.isArray(value))
return `new [] {${value.map(o => formatObject(o)).join(', ')}}`;
if (typeof value === 'object') {
const keys = Object.keys(value);
if (!keys.length)
return '';
const tokens: string[] = [];
for (const key of keys)
tokens.push(`${keys.length !== 1 ? indent : ''}${key}: ${formatObject(value[key], indent, key)}`);
return `\n${indent}${tokens.join(`,\n${indent}`)}`;
}
return String(value);
}
function formatObject(value: any, indent = ' ', name = ''): string { function formatObject(value: any, indent = ' ', name = ''): string {
if (typeof value === 'string') { if (typeof value === 'string') {
if (name === 'permissions' || name === 'colorScheme') if (['permissions', 'colorScheme', 'modifiers', 'button'].includes(name))
return `${getClassName(name)}.${toPascal(value)}`; return `${getClassName(name)}.${toPascal(value)}`;
return quote(value); return quote(value);
} }
@ -195,10 +165,12 @@ function formatObject(value: any, indent = ' ', name = ''): string {
if (typeof value === 'object') { if (typeof value === 'object') {
const keys = Object.keys(value); const keys = Object.keys(value);
if (!keys.length) if (!keys.length)
return ''; return name ? `new ${getClassName(name)}` : '';
const tokens: string[] = []; const tokens: string[] = [];
for (const key of keys) for (const key of keys) {
tokens.push(`${toPascal(key)} = ${formatObject(value[key], indent, key)},`); const property = getPropertyName(key);
tokens.push(`${property} = ${formatObject(value[key], indent, key)},`);
}
if (name) if (name)
return `new ${getClassName(name)}\n{\n${indent}${tokens.join(`\n${indent}`)}\n${indent}}`; return `new ${getClassName(name)}\n{\n${indent}${tokens.join(`\n${indent}`)}\n${indent}}`;
return `{\n${indent}${tokens.join(`\n${indent}`)}\n${indent}}`; return `{\n${indent}${tokens.join(`\n${indent}`)}\n${indent}}`;
@ -214,31 +186,37 @@ function getClassName(value: string): string {
case 'viewport': return 'ViewportSize'; case 'viewport': return 'ViewportSize';
case 'proxy': return 'ProxySettings'; case 'proxy': return 'ProxySettings';
case 'permissions': return 'ContextPermission'; case 'permissions': return 'ContextPermission';
case 'modifiers': return 'KeyboardModifier';
case 'button': return 'MouseButton';
default: return toPascal(value); default: return toPascal(value);
} }
} }
function getPropertyName(key: string): string {
switch (key) {
case 'storageState': return 'StorageStatePath';
case 'viewport': return 'ViewportSize';
default: return toPascal(key);
}
}
function toPascal(value: string): string { function toPascal(value: string): string {
return value[0].toUpperCase() + value.slice(1); return value[0].toUpperCase() + value.slice(1);
} }
function formatContextOptions(options: BrowserContextOptions, deviceName: string | undefined): string { function formatContextOptions(options: BrowserContextOptions, deviceName: string | undefined): string {
const device = deviceName && deviceDescriptors[deviceName]; const device = deviceName && deviceDescriptors[deviceName];
if (!device) if (!device) {
return formatArgs(options); if (!Object.entries(options).length)
const serializedObject = formatObject(sanitizeDeviceOptions(device, options), ' '); return '';
// When there are no additional context options, we still want to spread the device inside. return formatObject(options, ' ', 'BrowserNewContextOptions');
if (!serializedObject)
return `playwright.Devices["${deviceName}"]`;
let result = `new BrowserContextOptions(playwright.Devices["${deviceName}"])`;
if (serializedObject) {
const lines = serializedObject.split('\n');
result = `${result} \n${lines.join('\n')}`;
} }
return result; options = sanitizeDeviceOptions(device, options);
if (!Object.entries(options).length)
return `playwright.Devices[${quote(deviceName!)}]`;
return formatObject(options, ' ', `BrowserNewContextOptions(playwright.Devices[${quote(deviceName!)}])`);
} }
class CSharpFormatter { class CSharpFormatter {
@ -278,7 +256,7 @@ class CSharpFormatter {
line = spaces + extraSpaces + line; line = spaces + extraSpaces + line;
if (line.endsWith('{') || line.endsWith('[') || line.endsWith('(')) if (line.endsWith('{') || line.endsWith('[') || line.endsWith('('))
spaces += this._baseIndent; spaces += this._baseIndent;
if (line.endsWith('});')) if (line.endsWith('));'))
spaces = spaces.substring(this._baseIndent.length); spaces = spaces.substring(this._baseIndent.length);
return this._baseOffset + line; return this._baseOffset + line;

View file

@ -108,6 +108,7 @@ export type PopupSignal = BaseSignal & {
export type DownloadSignal = BaseSignal & { export type DownloadSignal = BaseSignal & {
name: 'download', name: 'download',
downloadAlias: string,
}; };
export type DialogSignal = BaseSignal & { export type DialogSignal = BaseSignal & {

View file

@ -46,6 +46,7 @@ export class RecorderSupplement implements InstrumentationListener {
private _pageAliases = new Map<Page, string>(); private _pageAliases = new Map<Page, string>();
private _lastPopupOrdinal = 0; private _lastPopupOrdinal = 0;
private _lastDialogOrdinal = 0; private _lastDialogOrdinal = 0;
private _lastDownloadOrdinal = 0;
private _timers = new Set<NodeJS.Timeout>(); private _timers = new Set<NodeJS.Timeout>();
private _context: BrowserContext; private _context: BrowserContext;
private _mode: Mode; private _mode: Mode;
@ -381,7 +382,7 @@ export class RecorderSupplement implements InstrumentationListener {
} }
private _onDownload(page: Page) { private _onDownload(page: Page) {
const pageAlias = this._pageAliases.get(page)!; const pageAlias = this._pageAliases.get(page)!;
this._generator.signal(pageAlias, page.mainFrame(), { name: 'download' }); this._generator.signal(pageAlias, page.mainFrame(), { name: 'download', downloadAlias: String(++this._lastDownloadOrdinal) });
} }
private _onDialog(page: Page) { private _onDialog(page: Page) {

View file

@ -51,8 +51,8 @@ test.describe('cli codegen', () => {
page.click("text=Submit");`); page.click("text=Submit");`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Click text=Submit // Click text=Submit
await page.ClickAsync("text=Submit");`); await page.ClickAsync("text=Submit");`);
expect(message.text()).toBe('click'); expect(message.text()).toBe('click');
}); });
@ -125,8 +125,8 @@ await page.ClickAsync("text=Submit");`);
page.click("text=Submit");`); page.click("text=Submit");`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Click text=Submit // Click text=Submit
await page.ClickAsync("text=Submit");`); await page.ClickAsync("text=Submit");`);
expect(message.text()).toBe('click'); expect(message.text()).toBe('click');
}); });
@ -194,8 +194,8 @@ await page.ClickAsync("text=Submit");`);
await page.fill(\"input[name=\\\"name\\\"]\", \"John\")`); await page.fill(\"input[name=\\\"name\\\"]\", \"John\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Fill input[name="name"] // Fill input[name="name"]
await page.FillAsync(\"input[name=\\\"name\\\"]\", \"John\");`); await page.FillAsync(\"input[name=\\\"name\\\"]\", \"John\");`);
expect(message.text()).toBe('John'); expect(message.text()).toBe('John');
}); });
@ -251,8 +251,8 @@ await page.FillAsync(\"input[name=\\\"name\\\"]\", \"John\");`);
await page.press(\"input[name=\\\"name\\\"]\", \"Shift+Enter\")`); await page.press(\"input[name=\\\"name\\\"]\", \"Shift+Enter\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Press Enter with modifiers // Press Enter with modifiers
await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`); await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
expect(messages[0].text()).toBe('press'); expect(messages[0].text()).toBe('press');
}); });
@ -369,8 +369,8 @@ await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
await page.check(\"input[name=\\\"accept\\\"]\")`); await page.check(\"input[name=\\\"accept\\\"]\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Check input[name="accept"] // Check input[name="accept"]
await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`); await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`);
expect(message.text()).toBe('true'); expect(message.text()).toBe('true');
}); });
@ -426,8 +426,8 @@ await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`);
await page.uncheck(\"input[name=\\\"accept\\\"]\")`); await page.uncheck(\"input[name=\\\"accept\\\"]\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Uncheck input[name="accept"] // Uncheck input[name="accept"]
await page.UncheckAsync(\"input[name=\\\"accept\\\"]\");`); await page.UncheckAsync(\"input[name=\\\"accept\\\"]\");`);
expect(message.text()).toBe('false'); expect(message.text()).toBe('false');
}); });
@ -463,8 +463,8 @@ await page.UncheckAsync(\"input[name=\\\"accept\\\"]\");`);
await page.select_option(\"select\", \"2\")`); await page.select_option(\"select\", \"2\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Select 2 // Select 2
await page.SelectOptionAsync(\"select\", \"2\");`); await page.SelectOptionAsync(\"select\", new[] { \"2\" });`);
expect(message.text()).toBe('2'); expect(message.text()).toBe('2');
}); });
@ -510,10 +510,10 @@ await page.SelectOptionAsync(\"select\", \"2\");`);
page1 = await popup_info.value`); page1 = await popup_info.value`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
var page1Task = page.WaitForEventAsync(PageEvent.Popup) var page1 = await page.RunAndWaitForEventAsync(PageEvent.Popup, async () =>
await Task.WhenAll( {
page1Task, await page.ClickAsync(\"text=link\");
page.ClickAsync(\"text=link\"));`); });`);
expect(popup.url()).toBe('about:blank'); expect(popup.url()).toBe('about:blank');
}); });
@ -552,9 +552,9 @@ await Task.WhenAll(
# assert page.url == \"about:blank#foo\"`); # assert page.url == \"about:blank#foo\"`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Click text=link // Click text=link
await page.ClickAsync(\"text=link\"); await page.ClickAsync(\"text=link\");
// Assert.Equal(\"about:blank#foo\", page.Url);`); // Assert.Equal(\"about:blank#foo\", page.Url);`);
expect(page.url()).toContain('about:blank#foo'); expect(page.url()).toContain('about:blank#foo');
}); });
@ -601,10 +601,13 @@ await page.ClickAsync(\"text=link\");
await page.click(\"text=link\")`); await page.click(\"text=link\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Click text=link // Click text=link
await Task.WhenAll( await Task.WhenAll(
page.WaitForNavigationAsync(/*\"about:blank#foo\"*/), page.WaitForNavigationAsync(/* new PageWaitForNavigationOptions
page.ClickAsync(\"text=link\"));`); {
UrlString = \"about:blank#foo\"
} */),
page.ClickAsync(\"text=link\"));`);
expect(page.url()).toContain('about:blank#foo'); expect(page.url()).toContain('about:blank#foo');
}); });
@ -622,4 +625,36 @@ await Task.WhenAll(
expect(recorder.sources().get('<javascript>').text).not.toContain(`await page.press('input', 'AltGraph');`); expect(recorder.sources().get('<javascript>').text).not.toContain(`await page.press('input', 'AltGraph');`);
expect(recorder.sources().get('<javascript>').text).toContain(`await page.fill('input', 'playwright@example.com');`); expect(recorder.sources().get('<javascript>').text).toContain(`await page.fill('input', 'playwright@example.com');`);
}); });
test('should middle click', async ({ page, openRecorder, server }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<a href${JSON.stringify(server.EMPTY_PAGE)}>Click me</a>`);
const [sources] = await Promise.all([
recorder.waitForOutput('<javascript>', 'click'),
page.click('a', { button: 'middle' }),
]);
expect(sources.get('<javascript>').text).toContain(`
await page.click('text=Click me', {
button: 'middle'
});`);
expect(sources.get('<python>').text).toContain(`
page.click("text=Click me", button="middle")`);
expect(sources.get('<async python>').text).toContain(`
await page.click("text=Click me", button="middle")`);
// TODO: fix this for java
// expect(sources.get('<java>').text).toContain(`
// page.click("text=Click me", foo);`);
expect(sources.get('<csharp>').text).toContain(`
await page.ClickAsync("text=Click me", new PageClickOptions
{
Button = MouseButton.Middle,
});`);
});
}); });

View file

@ -43,8 +43,8 @@ test.describe('cli codegen', () => {
page = await context.new_page()`); page = await context.new_page()`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Open new page // Open new page
var page = await context.NewPageAsync();`); var page = await context.NewPageAsync();`);
}); });
test('should contain second page', async ({ openRecorder, page }) => { test('should contain second page', async ({ openRecorder, page }) => {
@ -71,8 +71,8 @@ var page = await context.NewPageAsync();`);
page1 = await context.new_page()`); page1 = await context.new_page()`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Open new page // Open new page
var page1 = await context.NewPageAsync();`); var page1 = await context.NewPageAsync();`);
}); });
test('should contain close page', async ({ openRecorder, page }) => { test('should contain close page', async ({ openRecorder, page }) => {
@ -96,7 +96,7 @@ var page1 = await context.NewPageAsync();`);
await page.close()`); await page.close()`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
await page.CloseAsync();`); await page.CloseAsync();`);
}); });
test('should not lead to an error if html gets clicked', async ({ page, openRecorder }) => { test('should not lead to an error if html gets clicked', async ({ page, openRecorder }) => {
@ -147,9 +147,8 @@ await page.CloseAsync();`);
await page.set_input_files(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt\")`); await page.set_input_files(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Upload file-to-upload.txt // Upload file-to-upload.txt
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt\");`); await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { \"file-to-upload.txt\" });`);
}); });
test('should upload multiple files', async ({ page, openRecorder, browserName, asset }) => { test('should upload multiple files', async ({ page, openRecorder, browserName, asset }) => {
@ -185,8 +184,8 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt
await page.set_input_files(\"input[type=\\\"file\\\"]\", [\"file-to-upload.txt\", \"file-to-upload-2.txt\"]`); await page.set_input_files(\"input[type=\\\"file\\\"]\", [\"file-to-upload.txt\", \"file-to-upload-2.txt\"]`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Upload file-to-upload.txt, file-to-upload-2.txt // Upload file-to-upload.txt, file-to-upload-2.txt
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { \"file-to-upload.txt\", \"file-to-upload-2.txt\" });`); await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { \"file-to-upload.txt\", \"file-to-upload-2.txt\" });`);
}); });
test('should clear files', async ({ page, openRecorder, browserName, asset }) => { test('should clear files', async ({ page, openRecorder, browserName, asset }) => {
@ -222,8 +221,8 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { \"file-to-up
await page.set_input_files(\"input[type=\\\"file\\\"]\", []`); await page.set_input_files(\"input[type=\\\"file\\\"]\", []`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Clear selected files // Clear selected files
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { });`); await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { });`);
}); });
@ -277,11 +276,11 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { });`);
download = await download_info.value`); download = await download_info.value`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Click text=Download // Click text=Download
var downloadTask = page.WaitForEventAsync(PageEvent.Download); var download1 = await page.RunAndWaitForEventAsync(PageEvent.Download, async () =>
await Task.WhenAll( {
downloadTask, await page.ClickAsync(\"text=Download\");
page.ClickAsync(\"text=Download\"));`); });`);
}); });
test('should handle dialogs', async ({ page, openRecorder }) => { test('should handle dialogs', async ({ page, openRecorder }) => {
@ -325,15 +324,15 @@ await Task.WhenAll(
await page.click(\"text=click me\")`); await page.click(\"text=click me\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Click text=click me // Click text=click me
void page_Dialog1_EventHandler(object sender, DialogEventArgs e) void page_Dialog1_EventHandler(object sender, IDialog dialog)
{ {
Console.WriteLine($\"Dialog message: {e.Dialog.Message}\"); Console.WriteLine($\"Dialog message: {dialog.Message}\");
e.Dialog.DismissAsync(); dialog.DismissAsync();
page.Dialog -= page_Dialog1_EventHandler; page.Dialog -= page_Dialog1_EventHandler;
} }
page.Dialog += page_Dialog1_EventHandler; page.Dialog += page_Dialog1_EventHandler;
await page.ClickAsync(\"text=click me\");`); await page.ClickAsync(\"text=click me\");`);
}); });
@ -375,9 +374,9 @@ await page.ClickAsync(\"text=click me\");`);
page1 = await context.new_page() page1 = await context.new_page()
await page1.goto("about:blank?foo")`); await page1.goto("about:blank?foo")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Open new page // Open new page
var page1 = await context.NewPageAsync(); var page1 = await context.NewPageAsync();
await page1.GotoAsync("about:blank?foo");`); await page1.GotoAsync("about:blank?foo");`);
} else if (browserName === 'firefox') { } else if (browserName === 'firefox') {
expect(sources.get('<javascript>').text).toContain(` expect(sources.get('<javascript>').text).toContain(`
// Click text=link // Click text=link
@ -521,8 +520,8 @@ await page1.GotoAsync("about:blank?foo");`);
await page.frame(name=\"one\").click(\"text=Hi, I'm frame\")`); await page.frame(name=\"one\").click(\"text=Hi, I'm frame\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Click text=Hi, I'm frame // Click text=Hi, I'm frame
await page.GetFrame(name: \"one\").ClickAsync(\"text=Hi, I'm frame\");`); await page.Frame(\"one\").ClickAsync(\"text=Hi, I'm frame\");`);
[sources] = await Promise.all([ [sources] = await Promise.all([
recorder.waitForOutput('<javascript>', 'two'), recorder.waitForOutput('<javascript>', 'two'),
@ -548,8 +547,8 @@ await page.GetFrame(name: \"one\").ClickAsync(\"text=Hi, I'm frame\");`);
await page.frame(name=\"two\").click(\"text=Hi, I'm frame\")`); await page.frame(name=\"two\").click(\"text=Hi, I'm frame\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Click text=Hi, I'm frame // Click text=Hi, I'm frame
await page.GetFrame(name: \"two\").ClickAsync(\"text=Hi, I'm frame\");`); await page.Frame(\"two\").ClickAsync(\"text=Hi, I'm frame\");`);
[sources] = await Promise.all([ [sources] = await Promise.all([
recorder.waitForOutput('<javascript>', 'url: \''), recorder.waitForOutput('<javascript>', 'url: \''),
@ -575,8 +574,8 @@ await page.GetFrame(name: \"two\").ClickAsync(\"text=Hi, I'm frame\");`);
await page.frame(url=\"http://localhost:${server.PORT}/frames/frame.html\").click(\"text=Hi, I'm frame\")`); await page.frame(url=\"http://localhost:${server.PORT}/frames/frame.html\").click(\"text=Hi, I'm frame\")`);
expect(sources.get('<csharp>').text).toContain(` expect(sources.get('<csharp>').text).toContain(`
// Click text=Hi, I'm frame // Click text=Hi, I'm frame
await page.GetFrame(url: \"http://localhost:${server.PORT}/frames/frame.html\").ClickAsync(\"text=Hi, I'm frame\");`); await page.FrameByUrl(\"http://localhost:${server.PORT}/frames/frame.html\").ClickAsync(\"text=Hi, I'm frame\");`);
}); });
test('should record navigations after identical pushState', async ({ page, openRecorder, server }) => { test('should record navigations after identical pushState', async ({ page, openRecorder, server }) => {

View file

@ -20,7 +20,7 @@ import { test, expect } from './inspectorTest';
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString(); const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => { const launchOptions = (channel: string) => {
return channel ? `headless: false,\n channel: "${channel}"` : 'headless: false'; return channel ? `Headless = false,\n Channel = "${channel}",` : `Headless = false,`;
}; };
function capitalize(browserName: string): string { function capitalize(browserName: string): string {
@ -29,12 +29,12 @@ function capitalize(browserName: string): string {
test('should print the correct imports and context options', async ({ browserName, channel, runCLI }) => { test('should print the correct imports and context options', async ({ browserName, channel, runCLI }) => {
const cli = runCLI(['--target=csharp', emptyHTML]); const cli = runCLI(['--target=csharp', emptyHTML]);
const expectedResult = `await Playwright.InstallAsync(); const expectedResult = ` using var playwright = await Playwright.CreateAsync();
using var playwright = await Playwright.CreateAsync(); await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync( {
${launchOptions(channel)} ${launchOptions(channel)}
); });
var context = await browser.NewContextAsync();`; var context = await browser.NewContextAsync();`;
await cli.waitFor(expectedResult).catch(e => e); await cli.waitFor(expectedResult).catch(e => e);
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
@ -50,31 +50,34 @@ test('should print the correct context options for custom settings', async ({ br
'--viewport-size=1280,720', '--viewport-size=1280,720',
'--target=csharp', '--target=csharp',
emptyHTML]); emptyHTML]);
const expectedResult = `await Playwright.InstallAsync(); const expectedResult = `
using var playwright = await Playwright.CreateAsync(); using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync( await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
${launchOptions(channel)}, {
proxy: new ProxySettings ${launchOptions(channel)}
{ Proxy = new ProxySettings
Server = "http://myproxy:3128", {
} Server = "http://myproxy:3128",
); },
var context = await browser.NewContextAsync( });
viewport: new ViewportSize var context = await browser.NewContextAsync(new BrowserNewContextOptions
{ {
Width = 1280, ViewportSize = new ViewportSize
Height = 720, {
}, Width = 1280,
geolocation: new Geolocation Height = 720,
{ },
Latitude = 37.819722m, Geolocation = new Geolocation
Longitude = -122.478611m, {
}, Latitude = 37.819722m,
permissions: new[] { ContextPermission.Geolocation }, Longitude = -122.478611m,
userAgent: "hardkodemium", },
locale: "es", Permissions = new[] { ContextPermission.Geolocation },
colorScheme: ColorScheme.Dark, UserAgent = "hardkodemium",
timezoneId: "Europe/Rome");`; Locale = "es",
ColorScheme = ColorScheme.Dark,
TimezoneId = "Europe/Rome",
});`;
await cli.waitFor(expectedResult); await cli.waitFor(expectedResult);
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
@ -83,12 +86,13 @@ test('should print the correct context options when using a device', async ({ br
test.skip(browserName !== 'chromium'); test.skip(browserName !== 'chromium');
const cli = runCLI(['--device=Pixel 2', '--target=csharp', emptyHTML]); const cli = runCLI(['--device=Pixel 2', '--target=csharp', emptyHTML]);
const expectedResult = `await Playwright.InstallAsync(); const expectedResult = `
using var playwright = await Playwright.CreateAsync(); using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync( await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
${launchOptions(channel)} {
); ${launchOptions(channel)}
var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`; });
var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`;
await cli.waitFor(expectedResult); await cli.waitFor(expectedResult);
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
@ -107,33 +111,34 @@ test('should print the correct context options when using a device and additiona
'--viewport-size=1280,720', '--viewport-size=1280,720',
'--target=csharp', '--target=csharp',
emptyHTML]); emptyHTML]);
const expectedResult = `await Playwright.InstallAsync(); const expectedResult = `
using var playwright = await Playwright.CreateAsync(); using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Webkit.LaunchAsync( await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
${launchOptions(channel)}, {
proxy: new ProxySettings ${launchOptions(channel)}
{ Proxy = new ProxySettings
Server = "http://myproxy:3128", {
} Server = "http://myproxy:3128",
); },
var context = await browser.NewContextAsync(new BrowserContextOptions(playwright.Devices["iPhone 11"]) });
{ var context = await browser.NewContextAsync(new BrowserNewContextOptions(playwright.Devices["iPhone 11"])
UserAgent = "hardkodemium", {
Viewport = new ViewportSize UserAgent = "hardkodemium",
{ ViewportSize = new ViewportSize
Width = 1280, {
Height = 720, Width = 1280,
}, Height = 720,
Geolocation = new Geolocation },
{ Geolocation = new Geolocation
Latitude = 37.819722m, {
Longitude = -122.478611m, Latitude = 37.819722m,
}, Longitude = -122.478611m,
Permissions = new[] { ContextPermission.Geolocation }, },
Locale = "es", Permissions = new[] { ContextPermission.Geolocation },
ColorScheme = ColorScheme.Dark, Locale = "es",
TimezoneId = "Europe/Rome", ColorScheme = ColorScheme.Dark,
});`; TimezoneId = "Europe/Rome",
});`;
await cli.waitFor(expectedResult); await cli.waitFor(expectedResult);
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
@ -144,18 +149,22 @@ test('should print load/save storageState', async ({ browserName, channel, runCL
const saveFileName = testInfo.outputPath('save.json'); const saveFileName = testInfo.outputPath('save.json');
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8'); await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=csharp', emptyHTML]); const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=csharp', emptyHTML]);
const expectedResult1 = `await Playwright.InstallAsync(); const expectedResult1 = `
using var playwright = await Playwright.CreateAsync(); using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync( await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
${launchOptions(channel)} {
); ${launchOptions(channel)}
var context = await browser.NewContextAsync( });
storageState: "${loadFileName}");`; var context = await browser.NewContextAsync(new BrowserNewContextOptions
{
StorageStatePath = "${loadFileName}",
});`;
await cli.waitFor(expectedResult1); await cli.waitFor(expectedResult1);
const expectedResult2 = ` const expectedResult2 = `
// --------------------- await context.StorageStateAsync(new BrowserContextStorageStateOptions
await context.StorageStateAsync(path: "${saveFileName}"); {
Path = "${saveFileName}"
});
`; `;
await cli.waitFor(expectedResult2); await cli.waitFor(expectedResult2);
}); });