diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index f700bacc69..311825389e 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -283,21 +283,20 @@ class Recorder { if (this._mode !== 'recording') return true; const target = this._deepEventTarget(event); - if (['INPUT', 'TEXTAREA'].includes(target.nodeName)) { - const inputElement = target as HTMLInputElement; - const elementType = (inputElement.type || '').toLowerCase(); - if (['checkbox', 'radio'].includes(elementType)) { - // Checkbox is handled in click, we can't let input trigger on checkbox - that would mean we dispatched click events while recording. - return; - } - if (elementType === 'file') { - globalThis.__pw_recorderRecordAction({ - name: 'setInputFiles', - selector: this._activeModel!.selector, - signals: [], - files: [...(inputElement.files || [])].map(file => file.name), - }); + if (target.nodeName === 'INPUT' && (target as HTMLInputElement).type.toLowerCase() === 'file') { + globalThis.__pw_recorderRecordAction({ + name: 'setInputFiles', + selector: this._activeModel!.selector, + signals: [], + files: [...((target as HTMLInputElement).files || [])].map(file => file.name), + }); + return; + } + + if (['INPUT', 'TEXTAREA'].includes(target.nodeName) || target.isContentEditable) { + if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes((target as HTMLInputElement).type.toLowerCase())) { + // Checkbox is handled in click, we can't let input trigger on checkbox - that would mean we dispatched click events while recording. return; } @@ -308,7 +307,7 @@ class Recorder { name: 'fill', selector: this._activeModel!.selector, signals: [], - text: inputElement.value, + text: target.isContentEditable ? target.innerText : (target as HTMLInputElement).value, }); } diff --git a/tests/library/inspector/cli-codegen-1.spec.ts b/tests/library/inspector/cli-codegen-1.spec.ts index 71a0a52cdc..596f6108da 100644 --- a/tests/library/inspector/cli-codegen-1.spec.ts +++ b/tests/library/inspector/cli-codegen-1.spec.ts @@ -306,6 +306,23 @@ test.describe('cli codegen', () => { expect(message.text()).toBe('John'); }); + test('should fill [contentEditable]', async ({ page, openRecorder }) => { + const recorder = await openRecorder(); + + await recorder.setContentAndWait(`
`); + const locator = await recorder.focusElement('div'); + expect(locator).toBe(`locator('#content')`); + + const [message, sources] = await Promise.all([ + page.waitForEvent('console', msg => msg.type() !== 'error'), + recorder.waitForOutput('JavaScript', 'fill'), + page.fill('div', 'John Doe') + ]); + expect(sources.get('JavaScript').text).toContain(` + await page.locator('#content').fill('John Doe');`); + expect(message.text()).toBe('John Doe'); + }); + test('should press', async ({ page, openRecorder }) => { const recorder = await openRecorder(); diff --git a/tests/page/page-fill.spec.ts b/tests/page/page-fill.spec.ts index 556ff8cd97..3dc8ae74c2 100644 --- a/tests/page/page-fill.spec.ts +++ b/tests/page/page-fill.spec.ts @@ -159,6 +159,15 @@ it('should fill contenteditable', async ({ page, server }) => { expect(await page.$eval('div[contenteditable]', div => div.textContent)).toBe('some value'); }); +it('should fill contenteditable with new lines', async ({ page, server, browserName }) => { + it.fixme(browserName === 'firefox', 'Firefox does not handle new lines in contenteditable'); + + await page.goto(server.EMPTY_PAGE); + await page.setContent(``); + await page.locator('div[contenteditable]').fill('John\nDoe'); + expect(await page.locator('div[contenteditable]').innerText()).toBe('John\nDoe'); +}); + it('should fill elements with existing value and selection', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/textarea.html');