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') |     if (this._mode !== 'recording') | ||||||
|       return true; |       return true; | ||||||
|     const target = this._deepEventTarget(event); |     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({ |       globalThis.__pw_recorderRecordAction({ | ||||||
|         name: 'setInputFiles', |         name: 'setInputFiles', | ||||||
|         selector: this._activeModel!.selector, |         selector: this._activeModel!.selector, | ||||||
|         signals: [], |         signals: [], | ||||||
|           files: [...(inputElement.files || [])].map(file => file.name), |         files: [...((target as HTMLInputElement).files || [])].map(file => file.name), | ||||||
|       }); |       }); | ||||||
|       return; |       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.
 |       // Non-navigating actions are simply recorded by Playwright.
 | ||||||
|       if (this._consumedDueWrongTarget(event)) |       if (this._consumedDueWrongTarget(event)) | ||||||
|         return; |         return; | ||||||
|  | @ -308,7 +307,7 @@ class Recorder { | ||||||
|         name: 'fill', |         name: 'fill', | ||||||
|         selector: this._activeModel!.selector, |         selector: this._activeModel!.selector, | ||||||
|         signals: [], |         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'); |     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 }) => { |   test('should press', async ({ page, openRecorder }) => { | ||||||
|     const recorder = await 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'); |   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 }) => { | it('should fill elements with existing value and selection', async ({ page, server }) => { | ||||||
|   await page.goto(server.PREFIX + '/input/textarea.html'); |   await page.goto(server.PREFIX + '/input/textarea.html'); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue