fix(selector generator): do not reparent to invisible ancestor (#31590)

Fixes #31335.
This commit is contained in:
Dmitry Gozman 2024-07-08 09:07:25 -07:00 committed by GitHub
parent 4dfa55d1f1
commit 21c4531618
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 20 additions and 6 deletions

View File

@ -15,7 +15,7 @@
*/
import { cssEscape, escapeForAttributeSelector, escapeForTextSelector, escapeRegExp, quoteCSSAttributeValue } from '../../utils/isomorphic/stringUtils';
import { closestCrossShadow, isInsideScope, parentElementOrShadowHost } from './domUtils';
import { closestCrossShadow, isElementVisible, isInsideScope, parentElementOrShadowHost } from './domUtils';
import type { InjectedScript } from './injectedScript';
import { getAriaRole, getElementAccessibleName, beginAriaCaches, endAriaCaches } from './roleUtils';
import { elementText, getElementLabels } from './selectorUtils';
@ -89,7 +89,12 @@ export function generateSelector(injectedScript: InjectedScript, targetElement:
}
selectors = [joinTokens(targetTokens)];
} else {
targetElement = closestCrossShadow(targetElement, 'button,select,input,[role=button],[role=checkbox],[role=radio],a,[role=link]', options.root) || targetElement;
// Note: this matches InjectedScript.retarget().
if (!targetElement.matches('input,textarea,select') && !(targetElement as any).isContentEditable) {
const interactiveParent = closestCrossShadow(targetElement, 'button,select,input,[role=button],[role=checkbox],[role=radio],a,[role=link]', options.root);
if (interactiveParent && isElementVisible(interactiveParent))
targetElement = interactiveParent;
}
if (options.multiple) {
const withText = generateSelectorFor(injectedScript, targetElement, options);
const withoutText = generateSelectorFor(injectedScript, targetElement, { ...options, noText: true });

View File

@ -33,13 +33,22 @@ it.describe('selector generator', () => {
});
it('should prefer button over inner span', async ({ page }) => {
await page.setContent(`<button id=clickme><span></span></button>`);
expect(await generate(page, 'button')).toBe('#clickme');
await page.setContent(`<button><span>text</span></button>`);
expect(await generate(page, 'span')).toBe('internal:role=button[name="text"i]');
});
it('should prefer role=button over inner span', async ({ page }) => {
await page.setContent(`<div role=button><span></span></div>`);
expect(await generate(page, 'div')).toBe('internal:role=button');
await page.setContent(`<div role=button><span>text</span></div>`);
expect(await generate(page, 'span')).toBe('internal:role=button[name="text"i]');
});
it('should not prefer zero-sized button over inner span', async ({ page }) => {
await page.setContent(`
<button style="width:0;height:0;padding:0;border:0;overflow:visible;">
<span style="width:100px;height:100px;">text</span>
</button>
`);
expect(await generate(page, 'span')).toBe('internal:text="text"i');
});
it('should generate text and normalize whitespace', async ({ page }) => {