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