[recorder] Add a button to export steps as a Puppeteer script
Fixed: chromium:1209815
Change-Id: Id5d54c35fa057d494d72ce568cea9d0b6157e5c2
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2900441
Reviewed-by: Jan Scheffler <[email protected]>
Commit-Queue: Alex Rudenko <[email protected]>
diff --git a/front_end/core/i18n/locales/en-US.json b/front_end/core/i18n/locales/en-US.json
index 3566c60..ac2d193 100644
--- a/front_end/core/i18n/locales/en-US.json
+++ b/front_end/core/i18n/locales/en-US.json
@@ -8645,6 +8645,9 @@
"panels/sources/OutlineQuickOpen.ts | openAJavascriptOrCssFileToSee": {
"message": "Open a JavaScript or CSS file to see symbols"
},
+ "panels/sources/RecorderPlugin.ts | export": {
+ "message": "Export"
+ },
"panels/sources/RecorderPlugin.ts | play": {
"message": "Replay"
},
@@ -8804,6 +8807,9 @@
"panels/sources/sources-meta.ts | evaluateSelectedTextInConsole": {
"message": "Evaluate selected text in console"
},
+ "panels/sources/sources-meta.ts | exportRecording": {
+ "message": "Export"
+ },
"panels/sources/sources-meta.ts | filesystem": {
"message": "Filesystem"
},
diff --git a/front_end/core/i18n/locales/en-XL.json b/front_end/core/i18n/locales/en-XL.json
index 5bb7811..d747b1c 100644
--- a/front_end/core/i18n/locales/en-XL.json
+++ b/front_end/core/i18n/locales/en-XL.json
@@ -8645,6 +8645,9 @@
"panels/sources/OutlineQuickOpen.ts | openAJavascriptOrCssFileToSee": {
"message": "Ôṕêń â J́âv́âŚĉŕîṕt̂ ór̂ ĆŜŚ f̂íl̂é t̂ó ŝéê śŷḿb̂ól̂ś"
},
+ "panels/sources/RecorderPlugin.ts | export": {
+ "message": "Êx́p̂ór̂t́"
+ },
"panels/sources/RecorderPlugin.ts | play": {
"message": "R̂ép̂ĺâý"
},
@@ -8804,6 +8807,9 @@
"panels/sources/sources-meta.ts | evaluateSelectedTextInConsole": {
"message": "Êv́âĺûát̂é ŝél̂éĉt́êd́ t̂éx̂t́ îń ĉón̂śôĺê"
},
+ "panels/sources/sources-meta.ts | exportRecording": {
+ "message": "Êx́p̂ór̂t́"
+ },
"panels/sources/sources-meta.ts | filesystem": {
"message": "F̂íl̂éŝýŝt́êḿ"
},
diff --git a/front_end/models/recorder/RecorderModel.ts b/front_end/models/recorder/RecorderModel.ts
index 3886c15..ea61d50 100644
--- a/front_end/models/recorder/RecorderModel.ts
+++ b/front_end/models/recorder/RecorderModel.ts
@@ -6,7 +6,9 @@
import * as Common from '../../core/common/common.js';
import * as SDK from '../../core/sdk/sdk.js';
+import * as Bindings from '../../models/bindings/bindings.js';
import * as UI from '../../ui/legacy/legacy.js';
+
import type * as Workspace from '../workspace/workspace.js';
import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
@@ -14,6 +16,7 @@
import {RecordingSession} from './RecordingSession.js';
import type {Step} from './Steps.js';
import {ClickStep, NavigationStep, StepFrameContext, SubmitStep, ChangeStep, CloseStep, EmulateNetworkConditions} from './Steps.js';
+import {RecordingScriptWriter} from './RecordingScriptWriter.js';
const enum RecorderState {
Recording = 'Recording',
@@ -126,6 +129,22 @@
this._currentRecordingSession.stop();
this._currentRecordingSession = null;
}
+
+ async exportRecording(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
+ const script = this.parseScript(uiSourceCode.content());
+ const writer = new RecordingScriptWriter(' ');
+ while (script.length) {
+ const step = script.shift();
+ step && writer.appendStep(step);
+ }
+ const filename = uiSourceCode.name();
+ const stream = new Bindings.FileUtils.FileOutputStream();
+ if (!await stream.open(filename + '.js')) {
+ return;
+ }
+ stream.write(writer.getScript());
+ stream.close();
+ }
}
SDK.SDKModel.SDKModel.register(RecorderModel, {capabilities: SDK.SDKModel.Capability.None, autostart: false});
diff --git a/front_end/panels/sources/RecorderPlugin.ts b/front_end/panels/sources/RecorderPlugin.ts
index b20a493..fe13e72 100644
--- a/front_end/panels/sources/RecorderPlugin.ts
+++ b/front_end/panels/sources/RecorderPlugin.ts
@@ -21,6 +21,10 @@
*@description Text to replay a recording
*/
play: 'Replay',
+ /**
+ *@description Text of a button to export as a Puppeteer script
+ */
+ export: 'Export',
};
const str_ = i18n.i18n.registerUIStrings('panels/sources/RecorderPlugin.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -45,7 +49,9 @@
toggleRecording.setText(i18nString(UIStrings.record));
const replayRecording = UI.Toolbar.Toolbar.createActionButtonForId('recorder.replay-recording');
replayRecording.setText(i18nString(UIStrings.play));
+ const exportRecording = UI.Toolbar.Toolbar.createActionButtonForId('recorder.export-recording');
+ exportRecording.setText(i18nString(UIStrings.export));
- return [toggleRecording, replayRecording];
+ return [toggleRecording, replayRecording, exportRecording];
}
}
diff --git a/front_end/panels/sources/SourcesPanel.ts b/front_end/panels/sources/SourcesPanel.ts
index 8a5b79b..b5daee9 100644
--- a/front_end/panels/sources/SourcesPanel.ts
+++ b/front_end/panels/sources/SourcesPanel.ts
@@ -659,6 +659,22 @@
recorderModel.replayRecording(uiSourceCode);
}
+ _exportRecording(): void {
+ const uiSourceCode = this._sourcesView.currentUISourceCode();
+ if (!uiSourceCode) {
+ return;
+ }
+ const target = UI.Context.Context.instance().flavor(SDK.SDKModel.Target);
+ if (!target) {
+ return;
+ }
+ const recorderModel = target.model(Recorder.RecorderModel.RecorderModel);
+ if (!recorderModel) {
+ return;
+ }
+ recorderModel.exportRecording(uiSourceCode);
+ }
+
_editorSelected(event: Common.EventTarget.EventTargetEvent): void {
const uiSourceCode = (event.data as Workspace.UISourceCode.UISourceCode);
if (this.editorView.mainWidget() &&
@@ -1277,6 +1293,10 @@
panel._replayRecording();
return true;
}
+ case 'recorder.export-recording': {
+ panel._exportRecording();
+ return true;
+ }
case 'debugger.toggle-breakpoints-active': {
panel._toggleBreakpointsActive();
return true;
diff --git a/front_end/panels/sources/sources-meta.ts b/front_end/panels/sources/sources-meta.ts
index e02a56d..14efcd3 100644
--- a/front_end/panels/sources/sources-meta.ts
+++ b/front_end/panels/sources/sources-meta.ts
@@ -137,6 +137,10 @@
*/
replayRecording: 'Replay',
/**
+ *@description Title of a button to export a recorded series of actions as a Puppeteer script
+ */
+ exportRecording: 'Export',
+ /**
*@description Text of an item that stops the running task
*/
stop: 'Stop',
@@ -778,6 +782,22 @@
});
UI.ActionRegistration.registerActionExtension({
+ actionId: 'recorder.export-recording',
+ experiment: Root.Runtime.ExperimentName.RECORDER,
+ category: UI.ActionRegistration.ActionCategory.RECORDER,
+ async loadActionDelegate() {
+ const Sources = await loadSourcesModule();
+ return Sources.SourcesPanel.DebuggingActionDelegate.instance();
+ },
+ title: i18nLazyString(UIStrings.exportRecording),
+ iconClass: UI.ActionRegistration.IconClass.LARGEICON_DOWNLOAD,
+ contextTypes() {
+ return maybeRetrieveContextTypes(Sources => [Sources.SourcesView.SourcesView]);
+ },
+ bindings: [],
+});
+
+UI.ActionRegistration.registerActionExtension({
category: UI.ActionRegistration.ActionCategory.DEBUGGER,
actionId: 'debugger.toggle-breakpoints-active',
iconClass: UI.ActionRegistration.IconClass.LARGE_ICON_DEACTIVATE_BREAKPOINTS,
diff --git a/front_end/ui/legacy/ActionRegistration.ts b/front_end/ui/legacy/ActionRegistration.ts
index f3c5866..7c1ae1f 100644
--- a/front_end/ui/legacy/ActionRegistration.ts
+++ b/front_end/ui/legacy/ActionRegistration.ts
@@ -221,6 +221,7 @@
LARGEICON_VISIBILITY = 'largeicon-visibility',
LARGEICON_PHONE = 'largeicon-phone',
LARGEICON_PLAY = 'largeicon-play',
+ LARGEICON_DOWNLOAD = 'largeicon-download',
LARGEICON_PAUSE = 'largeicon-pause',
LARGEICON_RESUME = 'largeicon-resume',
LARGEICON_TRASH_BIN = 'largeicon-trash-bin',