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')
|
if (state === 'hidden')
|
||||||
return !this.isVisible(element);
|
return !this.isVisible(element);
|
||||||
|
|
||||||
const disabled = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(element.nodeName) && element.hasAttribute('disabled');
|
const disabled = isElementDisabled(element);
|
||||||
if (state === 'disabled')
|
if (state === 'disabled')
|
||||||
return disabled;
|
return disabled;
|
||||||
if (state === 'enabled')
|
if (state === 'enabled')
|
||||||
|
|
@ -1182,4 +1182,35 @@ function deepEquals(a: any, b: any): boolean {
|
||||||
return false;
|
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;
|
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');
|
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 }) => {
|
it('should wait for disabled button', async ({ page }) => {
|
||||||
await page.setContent('<button><span>Target</span></button>');
|
await page.setContent('<button><span>Target</span></button>');
|
||||||
const span = await page.$('text=Target');
|
const span = await page.$('text=Target');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue