mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-11 12:33:45 +03:00
fix(selector generator): do not produce has-text="foo"s
(#21679)
There is no locator counterpart for it. Instead, produce a regex. Also fix locator generator to not produce incorrect locator in this case. Fixes #21649.
This commit is contained in:
parent
b149d132a6
commit
bde2e90973
@ -43,6 +43,7 @@ const kRoleWithNameScore = 140;
|
||||
const kAltTextScore = 160;
|
||||
const kTextScore = 180;
|
||||
const kTitleScore = 200;
|
||||
const kTextScoreRegex = 250;
|
||||
const kPlaceholderScoreExact = kPlaceholderScore + kExactPenalty;
|
||||
const kLabelScoreExact = kLabelScore + kExactPenalty;
|
||||
const kRoleWithNameScoreExact = kRoleWithNameScore + kExactPenalty;
|
||||
@ -268,11 +269,10 @@ function buildTextCandidates(injectedScript: InjectedScript, element: Element, i
|
||||
const candidates: SelectorToken[][] = [];
|
||||
|
||||
const escaped = escapeForTextSelector(text, false);
|
||||
const exactEscaped = escapeForTextSelector(text, true);
|
||||
|
||||
if (isTargetNode) {
|
||||
candidates.push([{ engine: 'internal:text', selector: escaped, score: kTextScore }]);
|
||||
candidates.push([{ engine: 'internal:text', selector: exactEscaped, score: kTextScoreExact }]);
|
||||
candidates.push([{ engine: 'internal:text', selector: escapeForTextSelector(text, true), score: kTextScoreExact }]);
|
||||
}
|
||||
|
||||
const ariaRole = getAriaRole(element);
|
||||
@ -289,7 +289,8 @@ function buildTextCandidates(injectedScript: InjectedScript, element: Element, i
|
||||
candidate.push({ engine: 'css', selector: element.nodeName.toLowerCase(), score: kCSSTagNameScore });
|
||||
}
|
||||
candidates.push([...candidate, { engine: 'internal:has-text', selector: escaped, score: kTextScore }]);
|
||||
candidates.push([...candidate, { engine: 'internal:has-text', selector: exactEscaped, score: kTextScoreExact }]);
|
||||
if (text.length <= 80)
|
||||
candidates.push([...candidate, { engine: 'internal:has-text', selector: '/^' + escapeRegExp(text) + '$/', score: kTextScoreRegex }]);
|
||||
penalizeScoreForLength(candidates);
|
||||
return candidates;
|
||||
}
|
||||
@ -467,3 +468,8 @@ function isGuidLike(id: string): boolean {
|
||||
}
|
||||
return transitionCount >= id.length / 4;
|
||||
}
|
||||
|
||||
function escapeRegExp(s: string) {
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
@ -75,8 +75,11 @@ function innerAsLocator(factory: LocatorFactory, parsed: ParsedSelector, isFrame
|
||||
}
|
||||
if (part.name === 'internal:has-text') {
|
||||
const { exact, text } = detectExact(part.body as string);
|
||||
tokens.push(factory.generateLocator(base, 'has-text', text, { exact }));
|
||||
continue;
|
||||
// There is no locator equivalent for strict has-text, leave it as is.
|
||||
if (!exact) {
|
||||
tokens.push(factory.generateLocator(base, 'has-text', text, { exact }));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (part.name === 'internal:has') {
|
||||
const inner = innerAsLocator(factory, (part.body as NestedSelectorBody).parsed);
|
||||
|
@ -347,6 +347,8 @@ it.describe(() => {
|
||||
javascript: `locator('div').filter({ hasText: 'Goodbye world' }).locator('span')`,
|
||||
python: 'locator("div").filter(has_text="Goodbye world").locator("span")',
|
||||
});
|
||||
|
||||
expect.soft(asLocator('javascript', 'div >> internal:has-text="foo"s', false)).toBe(`locator('div').locator('internal:has-text="foo"s')`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -162,6 +162,15 @@ it.describe('selector generator', () => {
|
||||
expect(await generate(page, 'a:has-text("Hello")')).toBe(`a >> internal:has-text="Hello world"i`);
|
||||
});
|
||||
|
||||
it('should use internal:has-text with regexp', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<span>Hello world</span>
|
||||
<div><div>Hello <span>world</span></div>extra</div>
|
||||
<a>Goodbye <span>world</span></a>
|
||||
`);
|
||||
expect(await generate(page, 'div div')).toBe(`div >> internal:has-text=/^Hello world$/`);
|
||||
});
|
||||
|
||||
it('should chain text after parent', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<div>Hello <span>world</span></div>
|
||||
|
Loading…
Reference in New Issue
Block a user