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:
Joel Einbinder 2021-11-22 14:25:06 -05:00 committed by GitHub
parent daae8d4863
commit 06ab3c0fda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 1 deletions

View File

@ -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;

View File

@ -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');