feat(codegen): prefer frame name over url when unique (#5175)
This commit is contained in:
parent
35baf335d8
commit
527286683f
|
|
@ -18,10 +18,13 @@ import type { BrowserContextOptions, LaunchOptions } from '../../../..';
|
||||||
import { Frame } from '../../frames';
|
import { Frame } from '../../frames';
|
||||||
import { LanguageGenerator } from './language';
|
import { LanguageGenerator } from './language';
|
||||||
import { Action, Signal } from './recorderActions';
|
import { Action, Signal } from './recorderActions';
|
||||||
|
import { describeFrame } from './utils';
|
||||||
|
|
||||||
export type ActionInContext = {
|
export type ActionInContext = {
|
||||||
pageAlias: string;
|
pageAlias: string;
|
||||||
frame: Frame;
|
frameName?: string;
|
||||||
|
frameUrl: string;
|
||||||
|
isMainFrame: boolean;
|
||||||
action: Action;
|
action: Action;
|
||||||
committed?: boolean;
|
committed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +127,7 @@ export class CodeGenerator {
|
||||||
if (signal.name === 'navigation') {
|
if (signal.name === 'navigation') {
|
||||||
this.addAction({
|
this.addAction({
|
||||||
pageAlias,
|
pageAlias,
|
||||||
frame,
|
...describeFrame(frame),
|
||||||
committed: true,
|
committed: true,
|
||||||
action: {
|
action: {
|
||||||
name: 'navigate',
|
name: 'navigate',
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import deviceDescriptors = require('../../deviceDescriptors');
|
||||||
export class CSharpLanguageGenerator implements LanguageGenerator {
|
export class CSharpLanguageGenerator implements LanguageGenerator {
|
||||||
|
|
||||||
generateAction(actionInContext: ActionInContext, performingAction: boolean): string {
|
generateAction(actionInContext: ActionInContext, performingAction: boolean): string {
|
||||||
const { action, pageAlias, frame } = actionInContext;
|
const { action, pageAlias } = actionInContext;
|
||||||
const formatter = new CSharpFormatter(0);
|
const formatter = new CSharpFormatter(0);
|
||||||
formatter.newLine();
|
formatter.newLine();
|
||||||
formatter.add('// ' + actionTitle(action));
|
formatter.add('// ' + actionTitle(action));
|
||||||
|
|
@ -36,8 +36,10 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
const subject = !frame.parentFrame() ? pageAlias :
|
const subject = actionInContext.isMainFrame ? pageAlias :
|
||||||
`${pageAlias}.GetFrame(url: '${frame.url()}')`;
|
(actionInContext.frameName ?
|
||||||
|
`${pageAlias}.GetFrame(name: ${quote(actionInContext.frameName)})` :
|
||||||
|
`${pageAlias}.GetFrame(url: ${quote(actionInContext.frameUrl)})`);
|
||||||
|
|
||||||
let navigationSignal: NavigationSignal | undefined;
|
let navigationSignal: NavigationSignal | undefined;
|
||||||
let popupSignal: PopupSignal | undefined;
|
let popupSignal: PopupSignal | undefined;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import deviceDescriptors = require('../../deviceDescriptors');
|
||||||
export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
||||||
|
|
||||||
generateAction(actionInContext: ActionInContext, performingAction: boolean): string {
|
generateAction(actionInContext: ActionInContext, performingAction: boolean): string {
|
||||||
const { action, pageAlias, frame } = actionInContext;
|
const { action, pageAlias } = actionInContext;
|
||||||
const formatter = new JavaScriptFormatter(2);
|
const formatter = new JavaScriptFormatter(2);
|
||||||
formatter.newLine();
|
formatter.newLine();
|
||||||
formatter.add('// ' + actionTitle(action));
|
formatter.add('// ' + actionTitle(action));
|
||||||
|
|
@ -36,8 +36,10 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
const subject = !frame.parentFrame() ? pageAlias :
|
const subject = actionInContext.isMainFrame ? pageAlias :
|
||||||
`${pageAlias}.frame(${formatObject({ url: frame.url() })})`;
|
(actionInContext.frameName ?
|
||||||
|
`${pageAlias}.frame(${formatObject({ name: actionInContext.frameName })})` :
|
||||||
|
`${pageAlias}.frame(${formatObject({ url: actionInContext.frameUrl })})`);
|
||||||
|
|
||||||
let navigationSignal: NavigationSignal | undefined;
|
let navigationSignal: NavigationSignal | undefined;
|
||||||
let popupSignal: PopupSignal | undefined;
|
let popupSignal: PopupSignal | undefined;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
generateAction(actionInContext: ActionInContext, performingAction: boolean): string {
|
generateAction(actionInContext: ActionInContext, performingAction: boolean): string {
|
||||||
const { action, pageAlias, frame } = actionInContext;
|
const { action, pageAlias } = actionInContext;
|
||||||
const formatter = new PythonFormatter(4);
|
const formatter = new PythonFormatter(4);
|
||||||
formatter.newLine();
|
formatter.newLine();
|
||||||
formatter.add('# ' + actionTitle(action));
|
formatter.add('# ' + actionTitle(action));
|
||||||
|
|
@ -45,8 +45,10 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
const subject = !frame.parentFrame() ? pageAlias :
|
const subject = actionInContext.isMainFrame ? pageAlias :
|
||||||
`${pageAlias}.frame(${formatOptions({ url: frame.url() }, false)})`;
|
(actionInContext.frameName ?
|
||||||
|
`${pageAlias}.frame(${formatOptions({ name: actionInContext.frameName }, false)})` :
|
||||||
|
`${pageAlias}.frame(${formatOptions({ url: actionInContext.frameUrl }, false)})`);
|
||||||
|
|
||||||
let navigationSignal: NavigationSignal | undefined;
|
let navigationSignal: NavigationSignal | undefined;
|
||||||
let popupSignal: PopupSignal | undefined;
|
let popupSignal: PopupSignal | undefined;
|
||||||
|
|
|
||||||
|
|
@ -46,3 +46,13 @@ export function toModifiers(modifiers: number): ('Alt' | 'Control' | 'Meta' | 'S
|
||||||
result.push('Shift');
|
result.push('Shift');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function describeFrame(frame: Frame): { frameName?: string, frameUrl: string, isMainFrame: boolean } {
|
||||||
|
const page = frame._page;
|
||||||
|
if (page.mainFrame() === frame)
|
||||||
|
return { isMainFrame: true, frameUrl: frame.url() };
|
||||||
|
const frames = page.frames().filter(f => f.name() === frame.name());
|
||||||
|
if (frames.length === 1 && frames[0] === frame)
|
||||||
|
return { isMainFrame: false, frameUrl: frame.url(), frameName: frame.name() };
|
||||||
|
return { isMainFrame: false, frameUrl: frame.url() };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import * as actions from './recorder/recorderActions';
|
import * as actions from './recorder/recorderActions';
|
||||||
import type * as channels from '../../protocol/channels';
|
import type * as channels from '../../protocol/channels';
|
||||||
import { CodeGenerator, ActionInContext } from './recorder/codeGenerator';
|
import { CodeGenerator, ActionInContext } from './recorder/codeGenerator';
|
||||||
import { toClickOptions, toModifiers } from './recorder/utils';
|
import { describeFrame, toClickOptions, toModifiers } from './recorder/utils';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
import { Frame } from '../frames';
|
import { Frame } from '../frames';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
|
|
@ -151,7 +151,7 @@ export class RecorderSupplement {
|
||||||
this._pageAliases.delete(page);
|
this._pageAliases.delete(page);
|
||||||
this._generator.addAction({
|
this._generator.addAction({
|
||||||
pageAlias,
|
pageAlias,
|
||||||
frame: page.mainFrame(),
|
...describeFrame(page.mainFrame()),
|
||||||
committed: true,
|
committed: true,
|
||||||
action: {
|
action: {
|
||||||
name: 'closePage',
|
name: 'closePage',
|
||||||
|
|
@ -174,7 +174,7 @@ export class RecorderSupplement {
|
||||||
if (!isPopup) {
|
if (!isPopup) {
|
||||||
this._generator.addAction({
|
this._generator.addAction({
|
||||||
pageAlias,
|
pageAlias,
|
||||||
frame: page.mainFrame(),
|
...describeFrame(page.mainFrame()),
|
||||||
committed: true,
|
committed: true,
|
||||||
action: {
|
action: {
|
||||||
name: 'openPage',
|
name: 'openPage',
|
||||||
|
|
@ -190,7 +190,7 @@ export class RecorderSupplement {
|
||||||
const controller = new ProgressController();
|
const controller = new ProgressController();
|
||||||
const actionInContext: ActionInContext = {
|
const actionInContext: ActionInContext = {
|
||||||
pageAlias: this._pageAliases.get(page)!,
|
pageAlias: this._pageAliases.get(page)!,
|
||||||
frame,
|
...describeFrame(frame),
|
||||||
action
|
action
|
||||||
};
|
};
|
||||||
this._generator.willPerformAction(actionInContext);
|
this._generator.willPerformAction(actionInContext);
|
||||||
|
|
@ -218,10 +218,9 @@ export class RecorderSupplement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _recordAction(frame: Frame, action: actions.Action) {
|
private async _recordAction(frame: Frame, action: actions.Action) {
|
||||||
// We are lacking frame.page() in
|
|
||||||
this._generator.addAction({
|
this._generator.addAction({
|
||||||
pageAlias: this._pageAliases.get(frame._page)!,
|
pageAlias: this._pageAliases.get(frame._page)!,
|
||||||
frame,
|
...describeFrame(frame),
|
||||||
action
|
action
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -597,4 +597,45 @@ describe('cli codegen', (test, { browserName, headful }) => {
|
||||||
page.click('input[id=checkbox]')
|
page.click('input[id=checkbox]')
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should prefer frame name', async ({ page, recorder, server }) => {
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<iframe src='./frames/frame.html' name='one'></iframe>
|
||||||
|
<iframe src='./frames/frame.html' name='two'></iframe>
|
||||||
|
<iframe src='./frames/frame.html'></iframe>
|
||||||
|
`, server.EMPTY_PAGE, 4);
|
||||||
|
const frameOne = page.frame({ name: 'one' });
|
||||||
|
const frameTwo = page.frame({ name: 'two' });
|
||||||
|
const otherFrame = page.frames().find(f => f !== page.mainFrame() && !f.name());
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
recorder.waitForOutput('one'),
|
||||||
|
frameOne.click('div'),
|
||||||
|
]);
|
||||||
|
expect(recorder.output()).toContain(`
|
||||||
|
// Click text="Hi, I'm frame"
|
||||||
|
await page.frame({
|
||||||
|
name: 'one'
|
||||||
|
}).click('text="Hi, I\\'m frame"');`);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
recorder.waitForOutput('two'),
|
||||||
|
frameTwo.click('div'),
|
||||||
|
]);
|
||||||
|
expect(recorder.output()).toContain(`
|
||||||
|
// Click text="Hi, I'm frame"
|
||||||
|
await page.frame({
|
||||||
|
name: 'two'
|
||||||
|
}).click('text="Hi, I\\'m frame"');`);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
recorder.waitForOutput('url: \''),
|
||||||
|
otherFrame.click('div'),
|
||||||
|
]);
|
||||||
|
expect(recorder.output()).toContain(`
|
||||||
|
// Click text="Hi, I'm frame"
|
||||||
|
await page.frame({
|
||||||
|
url: '${otherFrame.url()}'
|
||||||
|
}).click('text="Hi, I\\'m frame"');`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -127,15 +127,20 @@ class Recorder {
|
||||||
this._actionPerformedCallback = () => { };
|
this._actionPerformedCallback = () => { };
|
||||||
}
|
}
|
||||||
|
|
||||||
async setContentAndWait(content: string, url: string = 'about:blank') {
|
async setContentAndWait(content: string, url: string = 'about:blank', frameCount: number = 1) {
|
||||||
await this.setPageContentAndWait(this.page, content, url);
|
await this.setPageContentAndWait(this.page, content, url, frameCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setPageContentAndWait(page: Page, content: string, url: string = 'about:blank') {
|
async setPageContentAndWait(page: Page, content: string, url: string = 'about:blank', frameCount: number = 1) {
|
||||||
let callback;
|
let callback;
|
||||||
const result = new Promise(f => callback = f);
|
const result = new Promise(f => callback = f);
|
||||||
await page.goto(url);
|
await page.goto(url);
|
||||||
await page.exposeBinding('_recorderScriptReadyForTest', (source, arg) => callback(arg));
|
const frames = new Set<any>();
|
||||||
|
await page.exposeBinding('_recorderScriptReadyForTest', (source, arg) => {
|
||||||
|
frames.add(source.frame);
|
||||||
|
if (frames.size === frameCount)
|
||||||
|
callback(arg);
|
||||||
|
});
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
result,
|
result,
|
||||||
page.setContent(content)
|
page.setContent(content)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue