feat(java): implement codegen (#5692)

This commit is contained in:
Pavel Feldman 2021-03-03 14:32:09 -08:00 committed by GitHub
parent 5903e771da
commit d3eff50386
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 578 additions and 6 deletions

View File

@ -0,0 +1,211 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { BrowserContextOptions } from '../../../..';
import { LanguageGenerator, LanguageGeneratorOptions, toSignalMap } from './language';
import { ActionInContext } from './codeGenerator';
import { Action, actionTitle } from './recorderActions';
import { toModifiers } from './utils';
import deviceDescriptors = require('../../deviceDescriptors');
import { JavaScriptFormatter } from './javascript';
export class JavaLanguageGenerator implements LanguageGenerator {
id = 'java';
fileName = '<java>';
highlighter = 'java';
generateAction(actionInContext: ActionInContext): string {
const { action, pageAlias } = actionInContext;
const formatter = new JavaScriptFormatter(6);
formatter.newLine();
formatter.add('// ' + actionTitle(action));
if (action.name === 'openPage') {
formatter.add(`Page ${pageAlias} = context.newPage();`);
if (action.url && action.url !== 'about:blank' && action.url !== 'chrome://newtab/')
formatter.add(`${pageAlias}.navigate("${action.url}");`);
return formatter.format();
}
const subject = actionInContext.isMainFrame ? pageAlias :
(actionInContext.frameName ?
`${pageAlias}.frame(${quote(actionInContext.frameName)})` :
`${pageAlias}.frameByUrl(${quote(actionInContext.frameUrl)})`);
const signals = toSignalMap(action);
if (signals.dialog) {
formatter.add(` ${pageAlias}.onceDialog(dialog -> {
System.out.println(String.format("Dialog message: %s", dialog.message()));
dialog.dismiss();
});`);
}
const actionCall = this._generateActionCall(action);
let code = `${subject}.${actionCall};`;
if (signals.popup) {
code = `Page ${signals.popup.popupAlias} = ${pageAlias}.waitForPopup(() -> {
${code}
});`;
}
if (signals.download) {
code = `Download download = ${pageAlias}.waitForDownload(() -> {
${code}
});`;
}
if (signals.waitForNavigation) {
code = `
// ${pageAlias}.waitForNavigation(new Page.WaitForNavigationOptions().withUrl(${quote(signals.waitForNavigation.url)}), () ->
${pageAlias}.waitForNavigation(() -> {
${code}
});`;
}
formatter.add(code);
if (signals.assertNavigation)
formatter.add(`// assert ${pageAlias}.url().equals(${quote(signals.assertNavigation.url)});`);
return formatter.format();
}
private _generateActionCall(action: Action): string {
switch (action.name) {
case 'openPage':
throw Error('Not reached');
case 'closePage':
return 'close()';
case 'click': {
let method = 'click';
if (action.clickCount === 2)
method = 'dblclick';
return `${method}(${quote(action.selector)})`;
}
case 'check':
return `check(${quote(action.selector)})`;
case 'uncheck':
return `uncheck(${quote(action.selector)})`;
case 'fill':
return `fill(${quote(action.selector)}, ${quote(action.text)})`;
case 'setInputFiles':
return `setInputFiles(${quote(action.selector)}, ${formatPath(action.files.length === 1 ? action.files[0] : action.files)})`;
case 'press': {
const modifiers = toModifiers(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
return `press(${quote(action.selector)}, ${quote(shortcut)})`;
}
case 'navigate':
return `navigate(${quote(action.url)})`;
case 'select':
return `selectOption(${quote(action.selector)}, ${formatSelectOption(action.options.length > 1 ? action.options : action.options[0])})`;
}
}
generateHeader(options: LanguageGeneratorOptions): string {
const formatter = new JavaScriptFormatter();
formatter.add(`
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
public class Example {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.${options.browserName}().launch(${formatLaunchOptions(options.launchOptions)});
BrowserContext context = browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName)});`);
return formatter.format();
}
generateFooter(saveStorage: string | undefined): string {
const storageStateLine = saveStorage ? `\n context.storageState(new BrowserContext.StorageStateOptions().withPath(${quote(saveStorage)}));` : '';
return `\n // ---------------------${storageStateLine}
}
}
}`;
}
}
function formatPath(files: string | string[]): string {
if (Array.isArray(files)) {
if (files.length === 0)
return 'new Path[0]';
return `new Path[] {${files.map(s => 'Paths.get(' + quote(s) + ')').join(', ')}}`;
}
return `Paths.get(${quote(files)})`;
}
function formatSelectOption(options: string | string[]): string {
if (Array.isArray(options)) {
if (options.length === 0)
return 'new String[0]';
return `new String[] {${options.map(s => quote(s)).join(', ')}}`;
}
return quote(options);
}
function formatLaunchOptions(options: any): string {
const lines = [];
if (!Object.keys(options).length)
return '';
lines.push('new BrowserType.LaunchOptions()');
if (typeof options.headless === 'boolean')
lines.push(` .withHeadless(false)`);
return lines.join('\n');
}
function formatContextOptions(contextOptions: BrowserContextOptions, deviceName: string | undefined): string {
const lines = [];
if (!Object.keys(contextOptions).length && !deviceName)
return '';
const device = deviceName ? deviceDescriptors[deviceName] : {};
const options: BrowserContextOptions = { ...device, ...contextOptions };
lines.push('new Browser.NewContextOptions()');
if (options.colorScheme)
lines.push(` .withColorScheme(ColorScheme.${options.colorScheme.toUpperCase()})`);
if (options.geolocation)
lines.push(` .withGeolocation(${options.geolocation.latitude}, ${options.geolocation.longitude})`);
if (options.locale)
lines.push(` .withLocale("${options.locale}")`);
if (options.proxy)
lines.push(` .withProxy(new Proxy("${options.proxy.server}"))`);
if (options.timezoneId)
lines.push(` .withTimezoneId("${options.timezoneId}")`);
if (options.userAgent)
lines.push(` .withUserAgent("${options.userAgent}")`);
if (options.viewport)
lines.push(` .withViewportSize(${options.viewport.width}, ${options.viewport.height})`);
if (options.deviceScaleFactor)
lines.push(` .withDeviceScaleFactor(${options.deviceScaleFactor})`);
if (options.isMobile)
lines.push(` .withIsMobile(${options.isMobile})`);
if (options.hasTouch)
lines.push(` .withHasTouch(${options.hasTouch})`);
if (options.storageState)
lines.push(` .withStorageStatePath(Paths.get(${quote(options.storageState as string)}))`);
return lines.join('\n');
}
function quote(text: string, char: string = '\"') {
if (char === '\'')
return char + text.replace(/[']/g, '\\\'') + char;
if (char === '"')
return char + text.replace(/["]/g, '\\"') + char;
if (char === '`')
return char + text.replace(/[`]/g, '\\`') + char;
throw new Error('Invalid escape char');
}

View File

@ -193,7 +193,7 @@ function formatContextOptions(options: BrowserContextOptions, deviceName: string
return lines.join('\n');
}
class JavaScriptFormatter {
export class JavaScriptFormatter {
private _baseIndent: string;
private _baseOffset: string;
private _lines: string[] = [];
@ -224,10 +224,11 @@ class JavaScriptFormatter {
if (line.startsWith('}') || line.startsWith(']'))
spaces = spaces.substring(this._baseIndent.length);
const extraSpaces = /^(for|while|if).*\(.*\)$/.test(previousLine) ? this._baseIndent : '';
const extraSpaces = /^(for|while|if|try).*\(.*\)$/.test(previousLine) ? this._baseIndent : '';
previousLine = line;
line = spaces + extraSpaces + line;
const callCarryOver = line.startsWith('.with');
line = spaces + extraSpaces + (callCarryOver ? this._baseIndent : '') + line;
if (line.endsWith('{') || line.endsWith('['))
spaces += this._baseIndent;
return this._baseOffset + line;

View File

@ -22,6 +22,7 @@ import { describeFrame, toClickOptions, toModifiers } from './recorder/utils';
import { Page } from '../page';
import { Frame } from '../frames';
import { BrowserContext } from '../browserContext';
import { JavaLanguageGenerator } from './recorder/java';
import { JavaScriptLanguageGenerator } from './recorder/javascript';
import { CSharpLanguageGenerator } from './recorder/csharp';
import { PythonLanguageGenerator } from './recorder/python';
@ -81,6 +82,7 @@ export class RecorderSupplement {
const language = params.language || context._options.sdkLanguage;
const languages = new Set([
new JavaLanguageGenerator(),
new JavaScriptLanguageGenerator(),
new PythonLanguageGenerator(false),
new PythonLanguageGenerator(true),

View File

@ -3,5 +3,6 @@ var hljs = require('./core');
hljs.registerLanguage('javascript', require('./languages/javascript'));
hljs.registerLanguage('python', require('./languages/python'));
hljs.registerLanguage('csharp', require('./languages/csharp'));
hljs.registerLanguage('java', require('./languages/java'));
module.exports = hljs;

View File

@ -0,0 +1,176 @@
// https://docs.oracle.com/javase/specs/jls/se15/html/jls-3.html#jls-3.10
var decimalDigits = '[0-9](_*[0-9])*';
var frac = `\\.(${decimalDigits})`;
var hexDigits = '[0-9a-fA-F](_*[0-9a-fA-F])*';
var NUMERIC = {
className: 'number',
variants: [
// DecimalFloatingPointLiteral
// including ExponentPart
{ begin: `(\\b(${decimalDigits})((${frac})|\\.)?|(${frac}))` +
`[eE][+-]?(${decimalDigits})[fFdD]?\\b` },
// excluding ExponentPart
{ begin: `\\b(${decimalDigits})((${frac})[fFdD]?\\b|\\.([fFdD]\\b)?)` },
{ begin: `(${frac})[fFdD]?\\b` },
{ begin: `\\b(${decimalDigits})[fFdD]\\b` },
// HexadecimalFloatingPointLiteral
{ begin: `\\b0[xX]((${hexDigits})\\.?|(${hexDigits})?\\.(${hexDigits}))` +
`[pP][+-]?(${decimalDigits})[fFdD]?\\b` },
// DecimalIntegerLiteral
{ begin: '\\b(0|[1-9](_*[0-9])*)[lL]?\\b' },
// HexIntegerLiteral
{ begin: `\\b0[xX](${hexDigits})[lL]?\\b` },
// OctalIntegerLiteral
{ begin: '\\b0(_*[0-7])*[lL]?\\b' },
// BinaryIntegerLiteral
{ begin: '\\b0[bB][01](_*[01])*[lL]?\\b' },
],
relevance: 0
};
/*
Language: Java
Author: Vsevolod Solovyov <vsevolod.solovyov@gmail.com>
Category: common, enterprise
Website: https://www.java.com/
*/
function java(hljs) {
var JAVA_IDENT_RE = '[\u00C0-\u02B8a-zA-Z_$][\u00C0-\u02B8a-zA-Z_$0-9]*';
var GENERIC_IDENT_RE = JAVA_IDENT_RE + '(<' + JAVA_IDENT_RE + '(\\s*,\\s*' + JAVA_IDENT_RE + ')*>)?';
var KEYWORDS = 'false synchronized int abstract float private char boolean var static null if const ' +
'for true while long strictfp finally protected import native final void ' +
'enum else break transient catch instanceof byte super volatile case assert short ' +
'package default double public try this switch continue throws protected public private ' +
'module requires exports do';
var ANNOTATION = {
className: 'meta',
begin: '@' + JAVA_IDENT_RE,
contains: [
{
begin: /\(/,
end: /\)/,
contains: ["self"] // allow nested () inside our annotation
},
]
};
const NUMBER = NUMERIC;
return {
name: 'Java',
aliases: ['jsp'],
keywords: KEYWORDS,
illegal: /<\/|#/,
contains: [
hljs.COMMENT(
'/\\*\\*',
'\\*/',
{
relevance: 0,
contains: [
{
// eat up @'s in emails to prevent them to be recognized as doctags
begin: /\w+@/, relevance: 0
},
{
className: 'doctag',
begin: '@[A-Za-z]+'
}
]
}
),
// relevance boost
{
begin: /import java\.[a-z]+\./,
keywords: "import",
relevance: 2
},
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
hljs.APOS_STRING_MODE,
hljs.QUOTE_STRING_MODE,
{
className: 'class',
beginKeywords: 'class interface enum', end: /[{;=]/, excludeEnd: true,
keywords: 'class interface enum',
illegal: /[:"\[\]]/,
contains: [
{ beginKeywords: 'extends implements' },
hljs.UNDERSCORE_TITLE_MODE
]
},
{
// Expression keywords prevent 'keyword Name(...)' from being
// recognized as a function definition
beginKeywords: 'new throw return else',
relevance: 0
},
{
className: 'class',
begin: 'record\\s+' + hljs.UNDERSCORE_IDENT_RE + '\\s*\\(',
returnBegin: true,
excludeEnd: true,
end: /[{;=]/,
keywords: KEYWORDS,
contains: [
{ beginKeywords: "record" },
{
begin: hljs.UNDERSCORE_IDENT_RE + '\\s*\\(',
returnBegin: true,
relevance: 0,
contains: [hljs.UNDERSCORE_TITLE_MODE]
},
{
className: 'params',
begin: /\(/, end: /\)/,
keywords: KEYWORDS,
relevance: 0,
contains: [
hljs.C_BLOCK_COMMENT_MODE
]
},
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE
]
},
{
className: 'function',
begin: '(' + GENERIC_IDENT_RE + '\\s+)+' + hljs.UNDERSCORE_IDENT_RE + '\\s*\\(', returnBegin: true, end: /[{;=]/,
excludeEnd: true,
keywords: KEYWORDS,
contains: [
{
begin: hljs.UNDERSCORE_IDENT_RE + '\\s*\\(', returnBegin: true,
relevance: 0,
contains: [hljs.UNDERSCORE_TITLE_MODE]
},
{
className: 'params',
begin: /\(/, end: /\)/,
keywords: KEYWORDS,
relevance: 0,
contains: [
ANNOTATION,
hljs.APOS_STRING_MODE,
hljs.QUOTE_STRING_MODE,
NUMBER,
hljs.C_BLOCK_COMMENT_MODE
]
},
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE
]
},
NUMBER,
ANNOTATION
]
};
}
module.exports = java;

View File

@ -5,7 +5,7 @@ set +x
# Pick a stable release revision from here:
# https://github.com/highlightjs/highlight.js/releases
RELEASE_REVISION="af20048d5c601d6e30016d8171317bfdf8a6c242"
LANGUAGES="javascript python csharp"
LANGUAGES="javascript python csharp java"
STYLES="tomorrow.css"
trap "cd $(pwd -P)" EXIT

View File

@ -47,6 +47,10 @@ describe('cli codegen', (suite, { browserName, headful, mode }) => {
# Click text=Submit
await page.click("text=Submit")`);
expect(sources.get('<java>').text).toContain(`
// Click text=Submit
page.click("text=Submit");`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=Submit
await page.ClickAsync("text=Submit");`);
@ -113,6 +117,10 @@ await page.ClickAsync("text=Submit");`);
# Click text=Submit
await page.click("text=Submit")`);
expect(sources.get('<java>').text).toContain(`
// Click text=Submit
page.click("text=Submit");`);
expect(sources.get('<csharp>').text).toContain(`
// Click text=Submit
await page.ClickAsync("text=Submit");`);
@ -166,6 +174,9 @@ await page.ClickAsync("text=Submit");`);
expect(sources.get('<javascript>').text).toContain(`
// Fill input[name="name"]
await page.fill('input[name="name"]', 'John');`);
expect(sources.get('<java>').text).toContain(`
// Fill input[name="name"]
page.fill("input[name=\\\"name\\\"]", "John");`);
expect(sources.get('<python>').text).toContain(`
# Fill input[name="name"]
@ -216,6 +227,10 @@ await page.FillAsync(\"input[name=\\\"name\\\"]\", \"John\");`);
// Press Enter with modifiers
await page.press('input[name="name"]', 'Shift+Enter');`);
expect(sources.get('<java>').text).toContain(`
// Press Enter with modifiers
page.press("input[name=\\\"name\\\"]", "Shift+Enter");`);
expect(sources.get('<python>').text).toContain(`
# Press Enter with modifiers
page.press(\"input[name=\\\"name\\\"]\", \"Shift+Enter\")`);
@ -321,6 +336,10 @@ await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
// Check input[name="accept"]
await page.check('input[name="accept"]');`);
expect(sources.get('<java>').text).toContain(`
// Check input[name="accept"]
page.check("input[name=\\\"accept\\\"]");`);
expect(sources.get('<python>').text).toContain(`
# Check input[name="accept"]
page.check(\"input[name=\\\"accept\\\"]\")`);
@ -370,6 +389,10 @@ await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`);
// Uncheck input[name="accept"]
await page.uncheck('input[name="accept"]');`);
expect(sources.get('<java>').text).toContain(`
// Uncheck input[name="accept"]
page.uncheck("input[name=\\\"accept\\\"]");`);
expect(sources.get('<python>').text).toContain(`
# Uncheck input[name="accept"]
page.uncheck(\"input[name=\\\"accept\\\"]\")`);
@ -401,6 +424,10 @@ await page.UncheckAsync(\"input[name=\\\"accept\\\"]\");`);
// Select 2
await page.selectOption('select', '2');`);
expect(sources.get('<java>').text).toContain(`
// Select 2
page.selectOption("select", "2");`);
expect(sources.get('<python>').text).toContain(`
# Select 2
page.select_option(\"select\", \"2\")`);
@ -437,6 +464,12 @@ await page.SelectOptionAsync(\"select\", \"2\");`);
page.click('text=link')
]);`);
expect(sources.get('<java>').text).toContain(`
// Click text=link
Page page1 = page.waitForPopup(() -> {
page.click("text=link");
});`);
expect(sources.get('<python>').text).toContain(`
# Click text=link
with page.expect_popup() as popup_info:
@ -474,6 +507,11 @@ await Task.WhenAll(
await page.click('text=link');
// assert.equal(page.url(), 'about:blank#foo');`);
expect(sources.get('<java>').text).toContain(`
// Click text=link
page.click("text=link");
// assert page.url().equals("about:blank#foo");`);
expect(sources.get('<python>').text).toContain(`
# Click text=link
page.click(\"text=link\")
@ -512,6 +550,13 @@ await page.ClickAsync(\"text=link\");
page.click('text=link')
]);`);
expect(sources.get('<java>').text).toContain(`
// Click text=link
// page.waitForNavigation(new Page.WaitForNavigationOptions().withUrl("about:blank#foo"), () ->
page.waitForNavigation(() -> {
page.click("text=link");
});`);
expect(sources.get('<python>').text).toContain(`
# Click text=link
# with page.expect_navigation(url=\"about:blank#foo\"):

View File

@ -31,6 +31,10 @@ describe('cli codegen', (suite, { mode }) => {
// Open new page
const page = await context.newPage();`);
expect(sources.get('<java>').text).toContain(`
// Open new page
Page page = context.newPage();`);
expect(sources.get('<python>').text).toContain(`
# Open new page
page = context.new_page()`);
@ -53,6 +57,10 @@ var page = await context.NewPageAsync();`);
// Open new page
const page1 = await context.newPage();`);
expect(sources.get('<java>').text).toContain(`
// Open new page
Page page1 = context.newPage();`);
expect(sources.get('<python>').text).toContain(`
# Open new page
page1 = context.new_page()`);
@ -75,6 +83,9 @@ var page1 = await context.NewPageAsync();`);
expect(sources.get('<javascript>').text).toContain(`
await page.close();`);
expect(sources.get('<java>').text).toContain(`
page.close();`);
expect(sources.get('<python>').text).toContain(`
page.close()`);
@ -117,6 +128,10 @@ await page.CloseAsync();`);
// Upload file-to-upload.txt
await page.setInputFiles('input[type="file"]', 'file-to-upload.txt');`);
expect(sources.get('<java>').text).toContain(`
// Upload file-to-upload.txt
page.setInputFiles("input[type=\\\"file\\\"]", Paths.get("file-to-upload.txt"));`);
expect(sources.get('<python>').text).toContain(`
# Upload file-to-upload.txt
page.set_input_files(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt\")`);
@ -150,6 +165,10 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt
// Upload file-to-upload.txt, file-to-upload-2.txt
await page.setInputFiles('input[type=\"file\"]', ['file-to-upload.txt', 'file-to-upload-2.txt']);`);
expect(sources.get('<java>').text).toContain(`
// Upload file-to-upload.txt, file-to-upload-2.txt
page.setInputFiles("input[type=\\\"file\\\"]", new Path[] {Paths.get("file-to-upload.txt"), Paths.get("file-to-upload-2.txt")});`);
expect(sources.get('<python>').text).toContain(`
# Upload file-to-upload.txt, file-to-upload-2.txt
page.set_input_files(\"input[type=\\\"file\\\"]\", [\"file-to-upload.txt\", \"file-to-upload-2.txt\"]`);
@ -182,6 +201,10 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { \"file-to-up
// Clear selected files
await page.setInputFiles('input[type=\"file\"]', []);`);
expect(sources.get('<java>').text).toContain(`
// Clear selected files
page.setInputFiles("input[type=\\\"file\\\"]", new Path[0]);`);
expect(sources.get('<python>').text).toContain(`
# Clear selected files
page.set_input_files(\"input[type=\\\"file\\\"]\", []`);
@ -225,6 +248,12 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { });`);
page.click('text=Download')
]);`);
expect(sources.get('<java>').text).toContain(`
// Click text=Download
Download download = page.waitForDownload(() -> {
page.click("text=Download");
});`);
expect(sources.get('<python>').text).toContain(`
# Click text=Download
with page.expect_download() as download_info:
@ -265,6 +294,14 @@ await Task.WhenAll(
});
await page.click('text=click me');`);
expect(sources.get('<java>').text).toContain(`
// Click text=click me
page.onceDialog(dialog -> {
System.out.println(String.format("Dialog message: %s", dialog.message()));
dialog.dismiss();
});
page.click("text=click me");`);
expect(sources.get('<python>').text).toContain(`
# Click text=click me
page.once(\"dialog\", lambda dialog: dialog.dismiss())
@ -359,6 +396,9 @@ await page.ClickAsync(\"text=click me\");`);
expect(sources.get('<javascript>').text).toContain(`await page1.fill('input', 'TextA');`);
expect(sources.get('<javascript>').text).toContain(`await page2.fill('input', 'TextB');`);
expect(sources.get('<java>').text).toContain(`page1.fill("input", "TextA");`);
expect(sources.get('<java>').text).toContain(`page2.fill("input", "TextB");`);
expect(sources.get('<python>').text).toContain(`page1.fill(\"input\", \"TextA\")`);
expect(sources.get('<python>').text).toContain(`page2.fill(\"input\", \"TextB\")`);
@ -438,6 +478,10 @@ await page.ClickAsync(\"text=click me\");`);
name: 'one'
}).click('text=Hi, I\\'m frame');`);
expect(sources.get('<java>').text).toContain(`
// Click text=Hi, I'm frame
page.frame("one").click("text=Hi, I'm frame");`);
expect(sources.get('<python>').text).toContain(`
# Click text=Hi, I'm frame
page.frame(name=\"one\").click(\"text=Hi, I'm frame\")`);
@ -461,6 +505,10 @@ await page.GetFrame(name: \"one\").ClickAsync(\"text=Hi, I'm frame\");`);
name: 'two'
}).click('text=Hi, I\\'m frame');`);
expect(sources.get('<java>').text).toContain(`
// Click text=Hi, I'm frame
page.frame("two").click("text=Hi, I'm frame");`);
expect(sources.get('<python>').text).toContain(`
# Click text=Hi, I'm frame
page.frame(name=\"two\").click(\"text=Hi, I'm frame\")`);
@ -484,6 +532,10 @@ await page.GetFrame(name: \"two\").ClickAsync(\"text=Hi, I'm frame\");`);
url: 'http://localhost:${server.PORT}/frames/frame.html'
}).click('text=Hi, I\\'m frame');`);
expect(sources.get('<java>').text).toContain(`
// Click text=Hi, I'm frame
page.frameByUrl("http://localhost:${server.PORT}/frames/frame.html").click("text=Hi, I'm frame");`);
expect(sources.get('<python>').text).toContain(`
# Click text=Hi, I'm frame
page.frame(url=\"http://localhost:${server.PORT}/frames/frame.html\").click(\"text=Hi, I'm frame\")`);

View File

@ -43,7 +43,6 @@ it('should print the correct context options for custom settings', async ({ brow
'--lang=es',
'--proxy-server=http://myproxy:3128',
'--timezone=Europe/Rome',
'--timeout=1000',
'--user-agent=hardkodemium',
'--viewport-size=1280,720',
'codegen',
@ -95,7 +94,6 @@ it('should print the correct context options when using a device and additional
'--lang=es',
'--proxy-server=http://myproxy:3128',
'--timezone=Europe/Rome',
'--timeout=1000',
'--user-agent=hardkodemium',
'--viewport-size=1280,720',
'codegen',

View File

@ -0,0 +1,86 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs';
import path from 'path';
import { folio } from './cli.fixtures';
const { it, expect } = folio;
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
it('should print the correct imports and context options', async ({ runCLI, browserName }) => {
const cli = runCLI(['codegen', '--target=java', emptyHTML]);
const expectedResult = `import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
public class Example {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.${browserName}().launch(new BrowserType.LaunchOptions()
.withHeadless(false));
BrowserContext context = browser.newContext();`;
await cli.waitFor(expectedResult);
expect(cli.text()).toContain(expectedResult);
});
it('should print the correct context options for custom settings', async ({ runCLI, browserName }) => {
const cli = runCLI(['--color-scheme=light', 'codegen', '--target=java', emptyHTML]);
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withColorScheme(ColorScheme.LIGHT));`;
await cli.waitFor(expectedResult);
expect(cli.text()).toContain(expectedResult);
});
it('should print the correct context options when using a device', async ({ runCLI }) => {
const cli = runCLI(['--device=Pixel 2', 'codegen', '--target=java', emptyHTML]);
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
.withViewportSize(411, 731)
.withDeviceScaleFactor(2.625)
.withIsMobile(true)
.withHasTouch(true));`;
await cli.waitFor(expectedResult);
expect(cli.text()).toContain(expectedResult);
});
it('should print the correct context options when using a device and additional options', async ({ runCLI }) => {
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', 'codegen', '--target=java', emptyHTML]);
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withColorScheme(ColorScheme.LIGHT)
.withUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1")
.withViewportSize(414, 896)
.withDeviceScaleFactor(2)
.withIsMobile(true)
.withHasTouch(true));`;
await cli.waitFor(expectedResult);
expect(cli.text()).toContain(expectedResult);
});
it('should print load/save storage_state', async ({ runCLI, browserName, testInfo }) => {
const loadFileName = testInfo.outputPath('load.json');
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}`, 'codegen', '--target=java', emptyHTML]);
const expectedResult1 = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withStorageStatePath(Paths.get("${loadFileName}")));`;
await cli.waitFor(expectedResult1);
const expectedResult2 = `
// ---------------------
context.storageState(new BrowserContext.StorageStateOptions().withPath("${saveFileName}"))`;
await cli.waitFor(expectedResult2);
});