diff --git a/src/server/supplements/recorder/codeGenerator.ts b/src/server/supplements/recorder/codeGenerator.ts
index 42a416b3b2430..4f8b349b7db92 100644
--- a/src/server/supplements/recorder/codeGenerator.ts
+++ b/src/server/supplements/recorder/codeGenerator.ts
@@ -18,10 +18,13 @@ import type { BrowserContextOptions, LaunchOptions } from '../../../..';
import { Frame } from '../../frames';
import { LanguageGenerator } from './language';
import { Action, Signal } from './recorderActions';
+import { describeFrame } from './utils';
export type ActionInContext = {
pageAlias: string;
- frame: Frame;
+ frameName?: string;
+ frameUrl: string;
+ isMainFrame: boolean;
action: Action;
committed?: boolean;
}
@@ -124,7 +127,7 @@ export class CodeGenerator {
if (signal.name === 'navigation') {
this.addAction({
pageAlias,
- frame,
+ ...describeFrame(frame),
committed: true,
action: {
name: 'navigate',
diff --git a/src/server/supplements/recorder/csharp.ts b/src/server/supplements/recorder/csharp.ts
index c653c93169b6a..23ed7683f9e25 100644
--- a/src/server/supplements/recorder/csharp.ts
+++ b/src/server/supplements/recorder/csharp.ts
@@ -24,7 +24,7 @@ import deviceDescriptors = require('../../deviceDescriptors');
export class CSharpLanguageGenerator implements LanguageGenerator {
generateAction(actionInContext: ActionInContext, performingAction: boolean): string {
- const { action, pageAlias, frame } = actionInContext;
+ const { action, pageAlias } = actionInContext;
const formatter = new CSharpFormatter(0);
formatter.newLine();
formatter.add('// ' + actionTitle(action));
@@ -36,8 +36,10 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
return formatter.format();
}
- const subject = !frame.parentFrame() ? pageAlias :
- `${pageAlias}.GetFrame(url: '${frame.url()}')`;
+ const subject = actionInContext.isMainFrame ? pageAlias :
+ (actionInContext.frameName ?
+ `${pageAlias}.GetFrame(name: ${quote(actionInContext.frameName)})` :
+ `${pageAlias}.GetFrame(url: ${quote(actionInContext.frameUrl)})`);
let navigationSignal: NavigationSignal | undefined;
let popupSignal: PopupSignal | undefined;
diff --git a/src/server/supplements/recorder/javascript.ts b/src/server/supplements/recorder/javascript.ts
index f35301a6a89d1..49021582e5f21 100644
--- a/src/server/supplements/recorder/javascript.ts
+++ b/src/server/supplements/recorder/javascript.ts
@@ -24,7 +24,7 @@ import deviceDescriptors = require('../../deviceDescriptors');
export class JavaScriptLanguageGenerator implements LanguageGenerator {
generateAction(actionInContext: ActionInContext, performingAction: boolean): string {
- const { action, pageAlias, frame } = actionInContext;
+ const { action, pageAlias } = actionInContext;
const formatter = new JavaScriptFormatter(2);
formatter.newLine();
formatter.add('// ' + actionTitle(action));
@@ -36,8 +36,10 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
return formatter.format();
}
- const subject = !frame.parentFrame() ? pageAlias :
- `${pageAlias}.frame(${formatObject({ url: frame.url() })})`;
+ const subject = actionInContext.isMainFrame ? pageAlias :
+ (actionInContext.frameName ?
+ `${pageAlias}.frame(${formatObject({ name: actionInContext.frameName })})` :
+ `${pageAlias}.frame(${formatObject({ url: actionInContext.frameUrl })})`);
let navigationSignal: NavigationSignal | undefined;
let popupSignal: PopupSignal | undefined;
diff --git a/src/server/supplements/recorder/python.ts b/src/server/supplements/recorder/python.ts
index e9a217c7b7161..9d4bc949dff53 100644
--- a/src/server/supplements/recorder/python.ts
+++ b/src/server/supplements/recorder/python.ts
@@ -33,7 +33,7 @@ export class PythonLanguageGenerator implements LanguageGenerator {
}
generateAction(actionInContext: ActionInContext, performingAction: boolean): string {
- const { action, pageAlias, frame } = actionInContext;
+ const { action, pageAlias } = actionInContext;
const formatter = new PythonFormatter(4);
formatter.newLine();
formatter.add('# ' + actionTitle(action));
@@ -45,8 +45,10 @@ export class PythonLanguageGenerator implements LanguageGenerator {
return formatter.format();
}
- const subject = !frame.parentFrame() ? pageAlias :
- `${pageAlias}.frame(${formatOptions({ url: frame.url() }, false)})`;
+ const subject = actionInContext.isMainFrame ? pageAlias :
+ (actionInContext.frameName ?
+ `${pageAlias}.frame(${formatOptions({ name: actionInContext.frameName }, false)})` :
+ `${pageAlias}.frame(${formatOptions({ url: actionInContext.frameUrl }, false)})`);
let navigationSignal: NavigationSignal | undefined;
let popupSignal: PopupSignal | undefined;
diff --git a/src/server/supplements/recorder/utils.ts b/src/server/supplements/recorder/utils.ts
index 264577dfe9cce..283aa3fef5c38 100644
--- a/src/server/supplements/recorder/utils.ts
+++ b/src/server/supplements/recorder/utils.ts
@@ -46,3 +46,13 @@ export function toModifiers(modifiers: number): ('Alt' | 'Control' | 'Meta' | 'S
result.push('Shift');
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() };
+}
diff --git a/src/server/supplements/recorderSupplement.ts b/src/server/supplements/recorderSupplement.ts
index cf9abb39b2115..56f4820d656c4 100644
--- a/src/server/supplements/recorderSupplement.ts
+++ b/src/server/supplements/recorderSupplement.ts
@@ -17,7 +17,7 @@
import * as actions from './recorder/recorderActions';
import type * as channels from '../../protocol/channels';
import { CodeGenerator, ActionInContext } from './recorder/codeGenerator';
-import { toClickOptions, toModifiers } from './recorder/utils';
+import { describeFrame, toClickOptions, toModifiers } from './recorder/utils';
import { Page } from '../page';
import { Frame } from '../frames';
import { BrowserContext } from '../browserContext';
@@ -151,7 +151,7 @@ export class RecorderSupplement {
this._pageAliases.delete(page);
this._generator.addAction({
pageAlias,
- frame: page.mainFrame(),
+ ...describeFrame(page.mainFrame()),
committed: true,
action: {
name: 'closePage',
@@ -174,7 +174,7 @@ export class RecorderSupplement {
if (!isPopup) {
this._generator.addAction({
pageAlias,
- frame: page.mainFrame(),
+ ...describeFrame(page.mainFrame()),
committed: true,
action: {
name: 'openPage',
@@ -190,7 +190,7 @@ export class RecorderSupplement {
const controller = new ProgressController();
const actionInContext: ActionInContext = {
pageAlias: this._pageAliases.get(page)!,
- frame,
+ ...describeFrame(frame),
action
};
this._generator.willPerformAction(actionInContext);
@@ -218,10 +218,9 @@ export class RecorderSupplement {
}
private async _recordAction(frame: Frame, action: actions.Action) {
- // We are lacking frame.page() in
this._generator.addAction({
pageAlias: this._pageAliases.get(frame._page)!,
- frame,
+ ...describeFrame(frame),
action
});
}
diff --git a/test/cli/cli-codegen.spec.ts b/test/cli/cli-codegen.spec.ts
index 0555f0e3053cb..326417bfc4359 100644
--- a/test/cli/cli-codegen.spec.ts
+++ b/test/cli/cli-codegen.spec.ts
@@ -597,4 +597,45 @@ describe('cli codegen', (test, { browserName, headful }) => {
page.click('input[id=checkbox]')
]);
});
+
+ it('should prefer frame name', async ({ page, recorder, server }) => {
+ await recorder.setContentAndWait(`
+
+
+
+ `, 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"');`);
+ });
});
diff --git a/test/cli/cli.fixtures.ts b/test/cli/cli.fixtures.ts
index 6165e28e031b4..b38c0de0cc54d 100644
--- a/test/cli/cli.fixtures.ts
+++ b/test/cli/cli.fixtures.ts
@@ -127,15 +127,20 @@ class Recorder {
this._actionPerformedCallback = () => { };
}
- async setContentAndWait(content: string, url: string = 'about:blank') {
- await this.setPageContentAndWait(this.page, content, url);
+ async setContentAndWait(content: string, url: string = 'about:blank', frameCount: number = 1) {
+ 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;
const result = new Promise(f => callback = f);
await page.goto(url);
- await page.exposeBinding('_recorderScriptReadyForTest', (source, arg) => callback(arg));
+ const frames = new Set();
+ await page.exposeBinding('_recorderScriptReadyForTest', (source, arg) => {
+ frames.add(source.frame);
+ if (frames.size === frameCount)
+ callback(arg);
+ });
await Promise.all([
result,
page.setContent(content)