mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix: use no internal selectors for frame locators (#19964)
Fixes https://github.com/microsoft/playwright/issues/19406
This commit is contained in:
parent
4f837690e2
commit
a7495c3326
@ -34,6 +34,7 @@ const kExactPenalty = kTextScoreRange / 2;
|
|||||||
const kTestIdScore = 1; // testIdAttributeName
|
const kTestIdScore = 1; // testIdAttributeName
|
||||||
const kOtherTestIdScore = 2; // other data-test* attributes
|
const kOtherTestIdScore = 2; // other data-test* attributes
|
||||||
|
|
||||||
|
const kIframeByAttributeScore = 10;
|
||||||
|
|
||||||
const kBeginPenalizedScore = 50;
|
const kBeginPenalizedScore = 50;
|
||||||
const kPlaceholderScore = 100;
|
const kPlaceholderScore = 100;
|
||||||
@ -174,14 +175,40 @@ function generateSelectorFor(injectedScript: InjectedScript, targetElement: Elem
|
|||||||
|
|
||||||
function buildCandidates(injectedScript: InjectedScript, element: Element, testIdAttributeName: string, accessibleNameCache: Map<Element, boolean>): SelectorToken[] {
|
function buildCandidates(injectedScript: InjectedScript, element: Element, testIdAttributeName: string, accessibleNameCache: Map<Element, boolean>): SelectorToken[] {
|
||||||
const candidates: SelectorToken[] = [];
|
const candidates: SelectorToken[] = [];
|
||||||
if (element.getAttribute(testIdAttributeName))
|
|
||||||
candidates.push({ engine: 'internal:testid', selector: `[${testIdAttributeName}=${escapeForAttributeSelector(element.getAttribute(testIdAttributeName)!, true)}]`, score: kTestIdScore });
|
// Start of generic candidates which are compatible for Locators and FrameLocators:
|
||||||
|
|
||||||
for (const attr of ['data-testid', 'data-test-id', 'data-test']) {
|
for (const attr of ['data-testid', 'data-test-id', 'data-test']) {
|
||||||
if (attr !== testIdAttributeName && element.getAttribute(attr))
|
if (attr !== testIdAttributeName && element.getAttribute(attr))
|
||||||
candidates.push({ engine: 'css', selector: `[${attr}=${quoteAttributeValue(element.getAttribute(attr)!)}]`, score: kOtherTestIdScore });
|
candidates.push({ engine: 'css', selector: `[${attr}=${quoteAttributeValue(element.getAttribute(attr)!)}]`, score: kOtherTestIdScore });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const idAttr = element.getAttribute('id');
|
||||||
|
if (idAttr && !isGuidLike(idAttr))
|
||||||
|
candidates.push({ engine: 'css', selector: makeSelectorForId(idAttr), score: kCSSIdScore });
|
||||||
|
|
||||||
|
candidates.push({ engine: 'css', selector: cssEscape(element.nodeName.toLowerCase()), score: kCSSTagNameScore });
|
||||||
|
|
||||||
|
if (element.nodeName === 'IFRAME') {
|
||||||
|
for (const attribute of ['name', 'title']) {
|
||||||
|
if (element.getAttribute(attribute))
|
||||||
|
candidates.push({ engine: 'css', selector: `${cssEscape(element.nodeName.toLowerCase())}[${attribute}=${quoteAttributeValue(element.getAttribute(attribute)!)}]`, score: kIframeByAttributeScore });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get via testIdAttributeName via CSS selector.
|
||||||
|
if (element.getAttribute(testIdAttributeName))
|
||||||
|
candidates.push({ engine: 'css', selector: `[${testIdAttributeName}=${escapeForAttributeSelector(element.getAttribute(testIdAttributeName)!, true!)}]`, score: kTestIdScore });
|
||||||
|
|
||||||
|
penalizeScoreForLength([candidates]);
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything after that are candidates that are not applicable to iframes and designed for Locators only(getBy* methods):
|
||||||
|
|
||||||
|
// Get via testIdAttributeName via GetByTestId().
|
||||||
|
if (element.getAttribute(testIdAttributeName))
|
||||||
|
candidates.push({ engine: 'internal:testid', selector: `[${testIdAttributeName}=${escapeForAttributeSelector(element.getAttribute(testIdAttributeName)!, true)}]`, score: kTestIdScore });
|
||||||
|
|
||||||
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
|
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
|
||||||
const input = element as HTMLInputElement | HTMLTextAreaElement;
|
const input = element as HTMLInputElement | HTMLTextAreaElement;
|
||||||
if (input.placeholder) {
|
if (input.placeholder) {
|
||||||
@ -228,11 +255,6 @@ function buildCandidates(injectedScript: InjectedScript, element: Element, testI
|
|||||||
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(element.nodeName) && element.getAttribute('type') !== 'hidden')
|
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(element.nodeName) && element.getAttribute('type') !== 'hidden')
|
||||||
candidates.push({ engine: 'css', selector: cssEscape(element.nodeName.toLowerCase()), score: kCSSInputTypeNameScore + 1 });
|
candidates.push({ engine: 'css', selector: cssEscape(element.nodeName.toLowerCase()), score: kCSSInputTypeNameScore + 1 });
|
||||||
|
|
||||||
const idAttr = element.getAttribute('id');
|
|
||||||
if (idAttr && !isGuidLike(idAttr))
|
|
||||||
candidates.push({ engine: 'css', selector: makeSelectorForId(idAttr), score: kCSSIdScore });
|
|
||||||
|
|
||||||
candidates.push({ engine: 'css', selector: cssEscape(element.nodeName.toLowerCase()), score: kCSSTagNameScore });
|
|
||||||
penalizeScoreForLength([candidates]);
|
penalizeScoreForLength([candidates]);
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
@ -203,6 +203,134 @@ test.describe('cli codegen', () => {
|
|||||||
await page.FrameByUrl(\"about:blank\").GetByText(\"HelloNameAnonymous\").ClickAsync();`);
|
await page.FrameByUrl(\"about:blank\").GetByText(\"HelloNameAnonymous\").ClickAsync();`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should generate frame locators with title attribute', async ({ page, openRecorder, server }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<iframe title="hello world" srcdoc="<button>Click me</button>"></iframe>
|
||||||
|
`, server.EMPTY_PAGE, 1);
|
||||||
|
|
||||||
|
const [sources] = await Promise.all([
|
||||||
|
recorder.waitForOutput('JavaScript', 'Click me'),
|
||||||
|
page.frameLocator('[title="hello world"]').getByRole('button', { name: 'Click me' }).click(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(
|
||||||
|
`await page.frameLocator('iframe[title="hello world"]').getByRole('button', { name: 'Click me' }).click();`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(
|
||||||
|
`page.frameLocator(\"iframe[title=\\\"hello world\\\"]\").getByRole(AriaRole.BUTTON, new FrameLocator.GetByRoleOptions().setName(\"Click me\")).click();`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(
|
||||||
|
`page.frame_locator(\"iframe[title=\\\"hello world\\\"]\").get_by_role(\"button\", name=\"Click me\").click()`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(
|
||||||
|
`await page.frame_locator("iframe[title=\\\"hello world\\\"]").get_by_role("button", name="Click me").click()`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(
|
||||||
|
`await page.FrameLocator("iframe[title=\\\"hello world\\\"]").GetByRole(AriaRole.Button, new() { Name = "Click me" }).ClickAsync();`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate frame locators with name attribute', async ({ page, openRecorder, server }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<iframe name="hello world" srcdoc="<button>Click me</button>"></iframe>
|
||||||
|
`, server.EMPTY_PAGE, 1);
|
||||||
|
|
||||||
|
const [sources] = await Promise.all([
|
||||||
|
recorder.waitForOutput('JavaScript', 'Click me'),
|
||||||
|
page.frameLocator('[name="hello world"]').getByRole('button', { name: 'Click me' }).click(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(
|
||||||
|
`await page.frameLocator('iframe[name="hello world"]').getByRole('button', { name: 'Click me' }).click();`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(
|
||||||
|
`page.frameLocator(\"iframe[name=\\\"hello world\\\"]\").getByRole(AriaRole.BUTTON, new FrameLocator.GetByRoleOptions().setName(\"Click me\")).click();`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(
|
||||||
|
`page.frame_locator(\"iframe[name=\\\"hello world\\\"]\").get_by_role(\"button\", name=\"Click me\").click()`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(
|
||||||
|
`await page.frame_locator("iframe[name=\\\"hello world\\\"]").get_by_role("button", name="Click me").click()`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(
|
||||||
|
`await page.FrameLocator("iframe[name=\\\"hello world\\\"]").GetByRole(AriaRole.Button, new() { Name = "Click me" }).ClickAsync();`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate frame locators with id attribute', async ({ page, openRecorder, server }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<iframe id="hello-world" srcdoc="<button>Click me</button>"></iframe>
|
||||||
|
`, server.EMPTY_PAGE, 1);
|
||||||
|
|
||||||
|
const [sources] = await Promise.all([
|
||||||
|
recorder.waitForOutput('JavaScript', 'Click me'),
|
||||||
|
page.frameLocator('[id="hello-world"]').getByRole('button', { name: 'Click me' }).click(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(
|
||||||
|
`await page.frameLocator('#hello-world').getByRole('button', { name: 'Click me' }).click();`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(
|
||||||
|
`page.frameLocator(\"#hello-world\").getByRole(AriaRole.BUTTON, new FrameLocator.GetByRoleOptions().setName(\"Click me\")).click();`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(
|
||||||
|
`page.frame_locator(\"#hello-world\").get_by_role(\"button\", name=\"Click me\").click()`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(
|
||||||
|
`await page.frame_locator("#hello-world").get_by_role("button", name="Click me").click()`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(
|
||||||
|
`await page.FrameLocator("#hello-world").GetByRole(AriaRole.Button, new() { Name = "Click me" }).ClickAsync();`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate frame locators with testId', async ({ page, openRecorder, server }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<iframe data-testid="my-testid" srcdoc="<button>Click me</button>"></iframe>
|
||||||
|
`, server.EMPTY_PAGE, 1);
|
||||||
|
|
||||||
|
const [sources] = await Promise.all([
|
||||||
|
recorder.waitForOutput('JavaScript', 'my-testid'),
|
||||||
|
page.frameLocator('iframe[data-testid="my-testid"]').getByRole('button', { name: 'Click me' }).click(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(
|
||||||
|
`await page.frameLocator('[data-testid="my-testid"]').getByRole('button', { name: 'Click me' }).click();`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(
|
||||||
|
`page.frameLocator(\"[data-testid=\\\"my-testid\\\"]\").getByRole(AriaRole.BUTTON, new FrameLocator.GetByRoleOptions().setName(\"Click me\")).click();`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(
|
||||||
|
`page.frame_locator(\"[data-testid=\\\"my-testid\\\"]\").get_by_role(\"button\", name=\"Click me\").click()`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(
|
||||||
|
`await page.frame_locator("[data-testid=\\\"my-testid\\\"]").get_by_role("button", name="Click me").click()`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(
|
||||||
|
`await page.FrameLocator("[data-testid=\\\"my-testid\\\"]").GetByRole(AriaRole.Button, new() { Name = "Click me" }).ClickAsync();`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('should generate role locators undef frame locators', async ({ page, openRecorder, server }) => {
|
test('should generate role locators undef frame locators', async ({ page, openRecorder, server }) => {
|
||||||
const recorder = await openRecorder();
|
const recorder = await openRecorder();
|
||||||
await recorder.setContentAndWait(`<iframe id=frame1 srcdoc="<button>Submit</button>">`, server.EMPTY_PAGE, 2);
|
await recorder.setContentAndWait(`<iframe id=frame1 srcdoc="<button>Submit</button>">`, server.EMPTY_PAGE, 2);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user