feat: consider fieldset and aria-disabled when checking if an element is enabled (#9927)
Co-authored-by: Max Schmitt <max@schmitt.mx>
This commit is contained in:
		
							parent
							
								
									daae8d4863
								
							
						
					
					
						commit
						06ab3c0fda
					
				|  | @ -492,7 +492,7 @@ export class InjectedScript { | |||
|     if (state === 'hidden') | ||||
|       return !this.isVisible(element); | ||||
| 
 | ||||
|     const disabled = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(element.nodeName) && element.hasAttribute('disabled'); | ||||
|     const disabled = isElementDisabled(element); | ||||
|     if (state === 'disabled') | ||||
|       return disabled; | ||||
|     if (state === 'enabled') | ||||
|  | @ -1182,4 +1182,35 @@ function deepEquals(a: any, b: any): boolean { | |||
|   return false; | ||||
| } | ||||
| 
 | ||||
| function isElementDisabled(element: Element): boolean { | ||||
|   const isRealFormControl = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(element.nodeName); | ||||
|   if (isRealFormControl && element.hasAttribute('disabled')) | ||||
|     return true; | ||||
|   if (isRealFormControl && hasDisabledFieldSet(element)) | ||||
|     return true; | ||||
|   if (hasAriaDisabled(element)) | ||||
|     return true; | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| function hasDisabledFieldSet(element: Element|null): boolean { | ||||
|   if (!element) | ||||
|     return false; | ||||
|   if (element.tagName === 'FIELDSET' && element.hasAttribute('disabled')) | ||||
|     return true; | ||||
|   // fieldset does not work across shadow boundaries
 | ||||
|   return hasDisabledFieldSet(element.parentElement); | ||||
| } | ||||
| function hasAriaDisabled(element: Element|undefined): boolean { | ||||
|   if (!element) | ||||
|     return false; | ||||
|   const attribute = (element.getAttribute('aria-disabled') || '').toLowerCase(); | ||||
|   if (attribute === 'true') | ||||
|     return true; | ||||
|   if (attribute === 'false') | ||||
|     return false; | ||||
|   return hasAriaDisabled(parentElementOrShadowHost(element)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export default InjectedScript; | ||||
|  |  | |||
|  | @ -103,6 +103,39 @@ it('should throw waiting for enabled when detached', async ({ page }) => { | |||
|   expect(error.message).toContain('Element is not attached to the DOM'); | ||||
| }); | ||||
| 
 | ||||
| it('should wait for button with a disabled fieldset', async ({ page }) => { | ||||
|   await page.setContent('<fieldset disabled=true><button><span>Target</span></button></div>'); | ||||
|   const span = await page.$('text=Target'); | ||||
|   let done = false; | ||||
|   const promise = span.waitForElementState('enabled').then(() => done = true); | ||||
|   await giveItAChanceToResolve(page); | ||||
|   expect(done).toBe(false); | ||||
|   await span.evaluate(span => (span.parentElement.parentElement as HTMLFieldSetElement).disabled = false); | ||||
|   await promise; | ||||
| }); | ||||
| 
 | ||||
| it('should wait for aria enabled button', async ({ page }) => { | ||||
|   await page.setContent('<button aria-disabled=true><span>Target</span></button>'); | ||||
|   const span = await page.$('text=Target'); | ||||
|   let done = false; | ||||
|   const promise = span.waitForElementState('enabled').then(() => done = true); | ||||
|   await giveItAChanceToResolve(page); | ||||
|   expect(done).toBe(false); | ||||
|   await span.evaluate(span => span.parentElement.setAttribute('aria-disabled', 'false')); | ||||
|   await promise; | ||||
| }); | ||||
| 
 | ||||
| it('should wait for button with an aria-disabled parent', async ({ page }) => { | ||||
|   await page.setContent('<div aria-disabled=true><button><span>Target</span></button></div>'); | ||||
|   const span = await page.$('text=Target'); | ||||
|   let done = false; | ||||
|   const promise = span.waitForElementState('enabled').then(() => done = true); | ||||
|   await giveItAChanceToResolve(page); | ||||
|   expect(done).toBe(false); | ||||
|   await span.evaluate(span => span.parentElement.parentElement.setAttribute('aria-disabled', 'false')); | ||||
|   await promise; | ||||
| }); | ||||
| 
 | ||||
| it('should wait for disabled button', async ({ page }) => { | ||||
|   await page.setContent('<button><span>Target</span></button>'); | ||||
|   const span = await page.$('text=Target'); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue