diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 4b4333c334..5f19d08490 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -340,6 +340,9 @@ scheme.DebugControllerStateChangedEvent = tObject({ }); scheme.DebugControllerSourceChangedEvent = tObject({ text: tString, + header: tOptional(tString), + footer: tOptional(tString), + actions: tOptional(tArray(tString)), }); scheme.DebugControllerBrowsersChangedEvent = tObject({ browsers: tArray(tObject({ diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 383a199818..d48548ef77 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -231,6 +231,7 @@ class InspectingRecorderApp extends EmptyRecorderApp { override async setSources(sources: Source[]): Promise { const source = sources.find(s => s.id === this._debugController._codegenId); - this._debugController.emit(DebugController.Events.SourceChanged, source?.text || ''); + const { text, header, footer, actions } = source || { text: '' }; + this._debugController.emit(DebugController.Events.SourceChanged, { text, header, footer, actions }); } } diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index 438bf4836b..17235a6362 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -31,8 +31,8 @@ export class DebugControllerDispatcher extends Dispatcher { this._dispatchEvent('inspectRequested', { selector, locator }); }); - this._object.on(DebugController.Events.SourceChanged, text => { - this._dispatchEvent('sourceChanged', { text }); + this._object.on(DebugController.Events.SourceChanged, ({ text, header, footer, actions }) => { + this._dispatchEvent('sourceChanged', ({ text, header, footer, actions })); }); } diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 5865132069..e8320eeaa4 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -340,16 +340,20 @@ class ContextRecorder extends EventEmitter { generator.on('change', () => { this._recorderSources = []; for (const languageGenerator of this._orderedLanguages) { + const { header, footer, actions, text } = generator.generateStructure(languageGenerator); const source: Source = { isRecorded: true, label: languageGenerator.name, group: languageGenerator.groupName, id: languageGenerator.id, - text: generator.generateText(languageGenerator), + text, + header, + footer, + actions, language: languageGenerator.highlighter, highlight: [] }; - source.revealLine = source.text.split('\n').length - 1; + source.revealLine = text.split('\n').length - 1; this._recorderSources.push(source); if (languageGenerator === this._orderedLanguages[0]) this._throttledOutputFile?.setContent(source.text); diff --git a/packages/playwright-core/src/server/recorder/codeGenerator.ts b/packages/playwright-core/src/server/recorder/codeGenerator.ts index cb592c7570..e0df6aca96 100644 --- a/packages/playwright-core/src/server/recorder/codeGenerator.ts +++ b/packages/playwright-core/src/server/recorder/codeGenerator.ts @@ -158,15 +158,11 @@ export class CodeGenerator extends EventEmitter { } } - generateText(languageGenerator: LanguageGenerator) { - const text = []; - text.push(languageGenerator.generateHeader(this._options)); - for (const action of this._actions) { - const actionText = languageGenerator.generateAction(action); - if (actionText) - text.push(actionText); - } - text.push(languageGenerator.generateFooter(this._options.saveStorage)); - return text.join('\n'); + generateStructure(languageGenerator: LanguageGenerator) { + const header = languageGenerator.generateHeader(this._options); + const footer = languageGenerator.generateFooter(this._options.saveStorage); + const actions = this._actions.map(a => languageGenerator.generateAction(a)).filter(Boolean); + const text = [header, ...actions, footer].join('\n'); + return { header, footer, actions, text }; } } diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 77f76577f6..aaf46befbc 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -615,6 +615,9 @@ export type DebugControllerStateChangedEvent = { }; export type DebugControllerSourceChangedEvent = { text: string, + header?: string, + footer?: string, + actions?: string[], }; export type DebugControllerBrowsersChangedEvent = { browsers: { diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 767c451474..fb770f173a 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -716,6 +716,12 @@ DebugController: sourceChanged: parameters: text: string + header: string? + footer: string? + actions: + type: array? + items: string + # Deprecated browsersChanged: diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index ac8993e210..ec3a67d8de 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -63,6 +63,9 @@ export type Source = { revealLine?: number; // used to group the language generators group?: string; + header?: string; + footer?: string; + actions?: string[]; }; declare global { diff --git a/tests/library/debug-controller.spec.ts b/tests/library/debug-controller.spec.ts index 5243039e0e..14ddcd8f2b 100644 --- a/tests/library/debug-controller.spec.ts +++ b/tests/library/debug-controller.spec.ts @@ -161,6 +161,14 @@ test('should record', async ({ backend, connectedBrowser }) => { await page.getByRole('button').click(); await expect.poll(() => events[events.length - 1]).toEqual({ + header: `import { test, expect } from '@playwright/test'; + +test('test', async ({ page }) => {`, + footer: `});`, + actions: [ + ` await page.goto('about:blank');`, + ` await page.getByRole('button', { name: 'Submit' }).click();`, + ], text: `import { test, expect } from '@playwright/test'; test('test', async ({ page }) => {