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

View file

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

View file

@ -46,6 +46,7 @@ export class RecorderSupplement implements InstrumentationListener {
private _pageAliases = new Map<Page, string>();
private _lastPopupOrdinal = 0;
private _lastDialogOrdinal = 0;
private _lastDownloadOrdinal = 0;
private _timers = new Set<NodeJS.Timeout>();
private _context: BrowserContext;
private _mode: Mode;
@ -381,7 +382,7 @@ export class RecorderSupplement implements InstrumentationListener {
}
private _onDownload(page: 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) {

View file

@ -51,8 +51,8 @@ test.describe('cli codegen', () => {
page.click("text=Submit");`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=Submit
await page.ClickAsync("text=Submit");`);
// Click text=Submit
await page.ClickAsync("text=Submit");`);
expect(message.text()).toBe('click');
});
@ -125,8 +125,8 @@ await page.ClickAsync("text=Submit");`);
page.click("text=Submit");`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=Submit
await page.ClickAsync("text=Submit");`);
// Click text=Submit
await page.ClickAsync("text=Submit");`);
expect(message.text()).toBe('click');
});
@ -194,8 +194,8 @@ await page.ClickAsync("text=Submit");`);
await page.fill(\"input[name=\\\"name\\\"]\", \"John\")`);
expect(sources.get('<csharp>').text).toContain(`
// Fill input[name="name"]
await page.FillAsync(\"input[name=\\\"name\\\"]\", \"John\");`);
// Fill input[name="name"]
await page.FillAsync(\"input[name=\\\"name\\\"]\", \"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\")`);
expect(sources.get('<csharp>').text).toContain(`
// Press Enter with modifiers
await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
// Press Enter with modifiers
await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
expect(messages[0].text()).toBe('press');
});
@ -369,8 +369,8 @@ await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
await page.check(\"input[name=\\\"accept\\\"]\")`);
expect(sources.get('<csharp>').text).toContain(`
// Check input[name="accept"]
await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`);
// Check input[name="accept"]
await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`);
expect(message.text()).toBe('true');
});
@ -426,8 +426,8 @@ await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`);
await page.uncheck(\"input[name=\\\"accept\\\"]\")`);
expect(sources.get('<csharp>').text).toContain(`
// Uncheck input[name="accept"]
await page.UncheckAsync(\"input[name=\\\"accept\\\"]\");`);
// Uncheck input[name="accept"]
await page.UncheckAsync(\"input[name=\\\"accept\\\"]\");`);
expect(message.text()).toBe('false');
});
@ -463,8 +463,8 @@ await page.UncheckAsync(\"input[name=\\\"accept\\\"]\");`);
await page.select_option(\"select\", \"2\")`);
expect(sources.get('<csharp>').text).toContain(`
// Select 2
await page.SelectOptionAsync(\"select\", \"2\");`);
// Select 2
await page.SelectOptionAsync(\"select\", new[] { \"2\" });`);
expect(message.text()).toBe('2');
});
@ -510,10 +510,10 @@ await page.SelectOptionAsync(\"select\", \"2\");`);
page1 = await popup_info.value`);
expect(sources.get('<csharp>').text).toContain(`
var page1Task = page.WaitForEventAsync(PageEvent.Popup)
await Task.WhenAll(
page1Task,
page.ClickAsync(\"text=link\"));`);
var page1 = await page.RunAndWaitForEventAsync(PageEvent.Popup, async () =>
{
await page.ClickAsync(\"text=link\");
});`);
expect(popup.url()).toBe('about:blank');
});
@ -552,9 +552,9 @@ await Task.WhenAll(
# assert page.url == \"about:blank#foo\"`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=link
await page.ClickAsync(\"text=link\");
// Assert.Equal(\"about:blank#foo\", page.Url);`);
// Click text=link
await page.ClickAsync(\"text=link\");
// Assert.Equal(\"about:blank#foo\", page.Url);`);
expect(page.url()).toContain('about:blank#foo');
});
@ -601,10 +601,13 @@ await page.ClickAsync(\"text=link\");
await page.click(\"text=link\")`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=link
await Task.WhenAll(
page.WaitForNavigationAsync(/*\"about:blank#foo\"*/),
page.ClickAsync(\"text=link\"));`);
// Click text=link
await Task.WhenAll(
page.WaitForNavigationAsync(/* new PageWaitForNavigationOptions
{
UrlString = \"about:blank#foo\"
} */),
page.ClickAsync(\"text=link\"));`);
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).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()`);
expect(sources.get('<csharp>').text).toContain(`
// Open new page
var page = await context.NewPageAsync();`);
// Open new page
var page = await context.NewPageAsync();`);
});
test('should contain second page', async ({ openRecorder, page }) => {
@ -71,8 +71,8 @@ var page = await context.NewPageAsync();`);
page1 = await context.new_page()`);
expect(sources.get('<csharp>').text).toContain(`
// Open new page
var page1 = await context.NewPageAsync();`);
// Open new page
var page1 = await context.NewPageAsync();`);
});
test('should contain close page', async ({ openRecorder, page }) => {
@ -96,7 +96,7 @@ var page1 = await context.NewPageAsync();`);
await page.close()`);
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 }) => {
@ -147,9 +147,8 @@ await page.CloseAsync();`);
await page.set_input_files(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt\")`);
expect(sources.get('<csharp>').text).toContain(`
// Upload file-to-upload.txt
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt\");`);
// Upload 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 }) => {
@ -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\"]`);
expect(sources.get('<csharp>').text).toContain(`
// 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\" });`);
// 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\" });`);
});
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\\\"]\", []`);
expect(sources.get('<csharp>').text).toContain(`
// Clear selected files
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { });`);
// Clear selected files
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { });`);
});
@ -277,11 +276,11 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { });`);
download = await download_info.value`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=Download
var downloadTask = page.WaitForEventAsync(PageEvent.Download);
await Task.WhenAll(
downloadTask,
page.ClickAsync(\"text=Download\"));`);
// Click text=Download
var download1 = await page.RunAndWaitForEventAsync(PageEvent.Download, async () =>
{
await page.ClickAsync(\"text=Download\");
});`);
});
test('should handle dialogs', async ({ page, openRecorder }) => {
@ -325,15 +324,15 @@ await Task.WhenAll(
await page.click(\"text=click me\")`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=click me
void page_Dialog1_EventHandler(object sender, DialogEventArgs e)
{
Console.WriteLine($\"Dialog message: {e.Dialog.Message}\");
e.Dialog.DismissAsync();
page.Dialog -= page_Dialog1_EventHandler;
}
page.Dialog += page_Dialog1_EventHandler;
await page.ClickAsync(\"text=click me\");`);
// Click text=click me
void page_Dialog1_EventHandler(object sender, IDialog dialog)
{
Console.WriteLine($\"Dialog message: {dialog.Message}\");
dialog.DismissAsync();
page.Dialog -= page_Dialog1_EventHandler;
}
page.Dialog += page_Dialog1_EventHandler;
await page.ClickAsync(\"text=click me\");`);
});
@ -375,9 +374,9 @@ await page.ClickAsync(\"text=click me\");`);
page1 = await context.new_page()
await page1.goto("about:blank?foo")`);
expect(sources.get('<csharp>').text).toContain(`
// Open new page
var page1 = await context.NewPageAsync();
await page1.GotoAsync("about:blank?foo");`);
// Open new page
var page1 = await context.NewPageAsync();
await page1.GotoAsync("about:blank?foo");`);
} else if (browserName === 'firefox') {
expect(sources.get('<javascript>').text).toContain(`
// Click text=link
@ -521,8 +520,8 @@ await page1.GotoAsync("about:blank?foo");`);
await page.frame(name=\"one\").click(\"text=Hi, I'm frame\")`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=Hi, I'm frame
await page.GetFrame(name: \"one\").ClickAsync(\"text=Hi, I'm frame\");`);
// Click text=Hi, I'm frame
await page.Frame(\"one\").ClickAsync(\"text=Hi, I'm frame\");`);
[sources] = await Promise.all([
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\")`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=Hi, I'm frame
await page.GetFrame(name: \"two\").ClickAsync(\"text=Hi, I'm frame\");`);
// Click text=Hi, I'm frame
await page.Frame(\"two\").ClickAsync(\"text=Hi, I'm frame\");`);
[sources] = await Promise.all([
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\")`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=Hi, I'm frame
await page.GetFrame(url: \"http://localhost:${server.PORT}/frames/frame.html\").ClickAsync(\"text=Hi, I'm frame\");`);
// Click 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 }) => {

View file

@ -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 ? `Headless = false,\n Channel = "${channel}",` : `Headless = false,`;
};
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 }) => {
const cli = runCLI(['--target=csharp', emptyHTML]);
const expectedResult = `await Playwright.InstallAsync();
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(
${launchOptions(channel)}
);
var context = await browser.NewContextAsync();`;
const expectedResult = ` using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
{
${launchOptions(channel)}
});
var context = await browser.NewContextAsync();`;
await cli.waitFor(expectedResult).catch(e => e);
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',
'--target=csharp',
emptyHTML]);
const expectedResult = `await Playwright.InstallAsync();
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(
${launchOptions(channel)},
proxy: new ProxySettings
{
Server = "http://myproxy:3128",
}
);
var context = await browser.NewContextAsync(
viewport: new ViewportSize
{
Width = 1280,
Height = 720,
},
geolocation: new Geolocation
{
Latitude = 37.819722m,
Longitude = -122.478611m,
},
permissions: new[] { ContextPermission.Geolocation },
userAgent: "hardkodemium",
locale: "es",
colorScheme: ColorScheme.Dark,
timezoneId: "Europe/Rome");`;
const expectedResult = `
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
{
${launchOptions(channel)}
Proxy = new ProxySettings
{
Server = "http://myproxy:3128",
},
});
var context = await browser.NewContextAsync(new BrowserNewContextOptions
{
ViewportSize = new ViewportSize
{
Width = 1280,
Height = 720,
},
Geolocation = new Geolocation
{
Latitude = 37.819722m,
Longitude = -122.478611m,
},
Permissions = new[] { ContextPermission.Geolocation },
UserAgent = "hardkodemium",
Locale = "es",
ColorScheme = ColorScheme.Dark,
TimezoneId = "Europe/Rome",
});`;
await cli.waitFor(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');
const cli = runCLI(['--device=Pixel 2', '--target=csharp', emptyHTML]);
const expectedResult = `await Playwright.InstallAsync();
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(
${launchOptions(channel)}
);
var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`;
const expectedResult = `
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
{
${launchOptions(channel)}
});
var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`;
await cli.waitFor(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',
'--target=csharp',
emptyHTML]);
const expectedResult = `await Playwright.InstallAsync();
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Webkit.LaunchAsync(
${launchOptions(channel)},
proxy: new ProxySettings
{
Server = "http://myproxy:3128",
}
);
var context = await browser.NewContextAsync(new BrowserContextOptions(playwright.Devices["iPhone 11"])
{
UserAgent = "hardkodemium",
Viewport = new ViewportSize
{
Width = 1280,
Height = 720,
},
Geolocation = new Geolocation
{
Latitude = 37.819722m,
Longitude = -122.478611m,
},
Permissions = new[] { ContextPermission.Geolocation },
Locale = "es",
ColorScheme = ColorScheme.Dark,
TimezoneId = "Europe/Rome",
});`;
const expectedResult = `
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
{
${launchOptions(channel)}
Proxy = new ProxySettings
{
Server = "http://myproxy:3128",
},
});
var context = await browser.NewContextAsync(new BrowserNewContextOptions(playwright.Devices["iPhone 11"])
{
UserAgent = "hardkodemium",
ViewportSize = new ViewportSize
{
Width = 1280,
Height = 720,
},
Geolocation = new Geolocation
{
Latitude = 37.819722m,
Longitude = -122.478611m,
},
Permissions = new[] { ContextPermission.Geolocation },
Locale = "es",
ColorScheme = ColorScheme.Dark,
TimezoneId = "Europe/Rome",
});`;
await cli.waitFor(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');
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=csharp', emptyHTML]);
const expectedResult1 = `await Playwright.InstallAsync();
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(
${launchOptions(channel)}
);
var context = await browser.NewContextAsync(
storageState: "${loadFileName}");`;
const expectedResult1 = `
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new BrowserTypeLaunchOptions
{
${launchOptions(channel)}
});
var context = await browser.NewContextAsync(new BrowserNewContextOptions
{
StorageStatePath = "${loadFileName}",
});`;
await cli.waitFor(expectedResult1);
const expectedResult2 = `
// ---------------------
await context.StorageStateAsync(path: "${saveFileName}");
await context.StorageStateAsync(new BrowserContextStorageStateOptions
{
Path = "${saveFileName}"
});
`;
await cli.waitFor(expectedResult2);
});