feat(recorder): make it work with [contenteditable] (#19066)
https://github.com/microsoft/playwright/issues/19029
This commit is contained in:
		
							parent
							
								
									1d3feba578
								
							
						
					
					
						commit
						3565d97a36
					
				|  | @ -283,24 +283,23 @@ 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') { | ||||
|     if (target.nodeName === 'INPUT' && (target as HTMLInputElement).type.toLowerCase() === 'file') { | ||||
|       globalThis.__pw_recorderRecordAction({ | ||||
|         name: 'setInputFiles', | ||||
|         selector: this._activeModel!.selector, | ||||
|         signals: [], | ||||
|           files: [...(inputElement.files || [])].map(file => file.name), | ||||
|         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; | ||||
|       } | ||||
| 
 | ||||
|       // Non-navigating actions are simply recorded by Playwright.
 | ||||
|       if (this._consumedDueWrongTarget(event)) | ||||
|         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, | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(`<div id="content" contenteditable="" oninput="console.log(content.innerText)"/>`); | ||||
|     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(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(`<div contenteditable="true"></div>`); | ||||
|   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'); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue