chore(codegen): prioritize role selectors (#17750)

This commit is contained in:
Pavel Feldman 2022-10-03 07:44:24 -08:00 committed by GitHub
parent ff6d240e83
commit 42a4d8a829
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 103 additions and 88 deletions

View File

@ -15,6 +15,7 @@
*/
import { type InjectedScript } from './injectedScript';
import { getAriaRole, getElementAccessibleName } from './roleUtils';
import { elementText } from './selectorUtils';
type SelectorToken = {
@ -45,7 +46,7 @@ export function querySelector(injectedScript: InjectedScript, selector: string,
export function generateSelector(injectedScript: InjectedScript, targetElement: Element, strict: boolean): { selector: string, elements: Element[] } {
injectedScript._evaluator.begin();
try {
targetElement = targetElement.closest('button,select,input,[role=button],[role=checkbox],[role=radio]') || targetElement;
targetElement = targetElement.closest('button,select,input,[role=button],[role=checkbox],[role=radio],a,[role=link]') || targetElement;
const targetTokens = generateSelectorFor(injectedScript, targetElement, strict);
const bestTokens = targetTokens || cssFallback(injectedScript, targetElement, strict);
const selector = joinTokens(bestTokens);
@ -70,6 +71,7 @@ function generateSelectorFor(injectedScript: InjectedScript, targetElement: Elem
if (targetElement.ownerDocument.documentElement === targetElement)
return [{ engine: 'css', selector: 'html', score: 1 }];
const accessibleNameCache = new Map();
const calculate = (element: Element, allowText: boolean): SelectorToken[] | null => {
const allowNthMatch = element === targetElement;
@ -78,7 +80,7 @@ function generateSelectorFor(injectedScript: InjectedScript, targetElement: Elem
// Do not use regex for parent elements (for performance).
textCandidates = filterRegexTokens(textCandidates);
}
const noTextCandidates = buildCandidates(injectedScript, element).map(token => [token]);
const noTextCandidates = buildCandidates(element, accessibleNameCache).map(token => [token]);
// First check all text and non-text candidates for the element.
let result = chooseFirstSelector(injectedScript, targetElement.ownerDocument, element, [...textCandidates, ...noTextCandidates], allowNthMatch, strict);
@ -141,32 +143,43 @@ function generateSelectorFor(injectedScript: InjectedScript, targetElement: Elem
return calculateCached(targetElement, true);
}
function buildCandidates(injectedScript: InjectedScript, element: Element): SelectorToken[] {
function buildCandidates(element: Element, accessibleNameCache: Map<Element, boolean>): SelectorToken[] {
const candidates: SelectorToken[] = [];
for (const attribute of ['data-testid', 'data-test-id', 'data-test']) {
if (element.getAttribute(attribute))
candidates.push({ engine: 'css', selector: `[${attribute}=${quoteAttributeValue(element.getAttribute(attribute)!)}]`, score: 1 });
if (element.getAttribute('data-testid'))
candidates.push({ engine: 'attr', selector: `[data-testid=${quoteAttributeValue(element.getAttribute('data-testid')!)}]`, score: 1 });
for (const attr of ['data-test-id', 'data-test']) {
if (element.getAttribute(attr))
candidates.push({ engine: 'css', selector: `[${attr}=${quoteAttributeValue(element.getAttribute(attr)!)}]`, score: 2 });
}
if (element.nodeName === 'INPUT') {
const input = element as HTMLInputElement;
if (input.placeholder)
candidates.push({ engine: 'css', selector: `[placeholder=${quoteAttributeValue(input.placeholder)}]`, score: 10 });
candidates.push({ engine: 'attr', selector: `[placeholder=${quoteAttributeValue(input.placeholder)}]`, score: 3 });
}
if (element.getAttribute('aria-label'))
candidates.push({ engine: 'css', selector: `[aria-label=${quoteAttributeValue(element.getAttribute('aria-label')!)}]`, score: 10 });
if (element.getAttribute('alt') && ['APPLET', 'AREA', 'IMG', 'INPUT'].includes(element.nodeName))
candidates.push({ engine: 'css', selector: `${cssEscape(element.nodeName.toLowerCase())}[alt=${quoteAttributeValue(element.getAttribute('alt')!)}]`, score: 10 });
if (element.getAttribute('role'))
candidates.push({ engine: 'css', selector: `${cssEscape(element.nodeName.toLowerCase())}[role=${quoteAttributeValue(element.getAttribute('role')!)}]`, score: 50 });
const ariaRole = getAriaRole(element);
if (ariaRole) {
const ariaName = getElementAccessibleName(element, false, accessibleNameCache);
if (ariaName)
candidates.push({ engine: 'role', selector: `${ariaRole}[name=${quoteAttributeValue(ariaName)}]`, score: 3 });
else
candidates.push({ engine: 'role', selector: ariaRole, score: 150 });
}
if (element.getAttribute('alt') && ['APPLET', 'AREA', 'IMG', 'INPUT'].includes(element.nodeName))
candidates.push({ engine: 'attr', selector: `[alt=${quoteAttributeValue(element.getAttribute('alt')!)}]`, score: 10 });
if (element.getAttribute('name') && ['BUTTON', 'FORM', 'FIELDSET', 'FRAME', 'IFRAME', 'INPUT', 'KEYGEN', 'OBJECT', 'OUTPUT', 'SELECT', 'TEXTAREA', 'MAP', 'META', 'PARAM'].includes(element.nodeName))
candidates.push({ engine: 'css', selector: `${cssEscape(element.nodeName.toLowerCase())}[name=${quoteAttributeValue(element.getAttribute('name')!)}]`, score: 50 });
if (['INPUT', 'TEXTAREA'].includes(element.nodeName) && element.getAttribute('type') !== 'hidden') {
if (element.getAttribute('type'))
candidates.push({ engine: 'css', selector: `${cssEscape(element.nodeName.toLowerCase())}[type=${quoteAttributeValue(element.getAttribute('type')!)}]`, score: 50 });
}
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(element.nodeName))
candidates.push({ engine: 'css', selector: cssEscape(element.nodeName.toLowerCase()), score: 50 });
@ -174,12 +187,11 @@ function buildCandidates(injectedScript: InjectedScript, element: Element): Sele
if (idAttr && !isGuidLike(idAttr))
candidates.push({ engine: 'css', selector: makeSelectorForId(idAttr), score: 100 });
candidates.push({ engine: 'css', selector: cssEscape(element.nodeName.toLowerCase()), score: 200 });
return candidates;
}
function buildTextCandidates(injectedScript: InjectedScript, element: Element, allowHasText: boolean): SelectorToken[] {
function buildTextCandidates(injectedScript: InjectedScript, element: Element, isTargetNode: boolean): SelectorToken[] {
if (element.nodeName === 'SELECT')
return [];
const text = elementText(injectedScript._evaluator._cacheText, element).full.trim().replace(/\s+/g, ' ').substring(0, 80);
@ -191,12 +203,14 @@ function buildTextCandidates(injectedScript: InjectedScript, element: Element, a
if (text.includes('"') || text.includes('>>') || text[0] === '/')
escaped = `/.*${escapeForRegex(text)}.*/`;
candidates.push({ engine: 'text', selector: escaped, score: 10 });
if (allowHasText && escaped === text) {
if (isTargetNode)
candidates.push({ engine: 'text', selector: escaped, score: 10 });
if (escaped === text) {
let prefix = element.nodeName.toLowerCase();
if (element.hasAttribute('role'))
prefix += `[role=${quoteAttributeValue(element.getAttribute('role')!)}]`;
candidates.push({ engine: 'css', selector: `${prefix}:has-text("${text}")`, score: 30 });
candidates.push({ engine: 'css', selector: `${prefix}:has-text("${text}")`, score: 10 });
}
return candidates;
}

View File

@ -26,7 +26,7 @@ test.describe('cli codegen', () => {
await recorder.setContentAndWait(`<button onclick="console.log('click')">Submit</button>`);
const selector = await recorder.hoverOverElement('button');
expect(selector).toBe('text=Submit');
expect(selector).toBe('role=button[name=\"Submit\"]');
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
@ -35,19 +35,19 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('text=Submit').click();`);
await page.locator('role=button[name=\"Submit\"]').click();`);
expect(sources.get('Python').text).toContain(`
page.locator("text=Submit").click()`);
page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
expect(sources.get('Python Async').text).toContain(`
await page.locator("text=Submit").click()`);
await page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
expect(sources.get('Java').text).toContain(`
page.locator("text=Submit").click();`);
page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
expect(sources.get('C#').text).toContain(`
await page.Locator("text=Submit").ClickAsync();`);
await page.Locator(\"role=button[name=\\\"Submit\\\"]\").ClickAsync();`);
expect(message.text()).toBe('click');
});
@ -69,7 +69,7 @@ test.describe('cli codegen', () => {
await page.waitForTimeout(1000);
const selector = await recorder.hoverOverElement('button');
expect(selector).toBe('text=Submit');
expect(selector).toBe('role=button[name=\"Submit\"]');
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
@ -78,7 +78,7 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('text=Submit').click();`);
await page.locator('role=button[name=\"Submit\"]').click();`);
expect(message.text()).toBe('click');
});
@ -149,7 +149,7 @@ test.describe('cli codegen', () => {
</body>`);
const selector = await recorder.hoverOverElement('button');
expect(selector).toBe('text=Submit');
expect(selector).toBe('role=button[name=\"Submit\"]');
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
@ -158,19 +158,19 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('text=Submit').click();`);
await page.locator('role=button[name=\"Submit\"]').click();`);
expect(sources.get('Python').text).toContain(`
page.locator("text=Submit").click()`);
page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
expect(sources.get('Python Async').text).toContain(`
await page.locator("text=Submit").click()`);
await page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
expect(sources.get('Java').text).toContain(`
page.locator("text=Submit").click();`);
page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
expect(sources.get('C#').text).toContain(`
await page.Locator("text=Submit").ClickAsync();`);
await page.Locator(\"role=button[name=\\\"Submit\\\"]\").ClickAsync();`);
expect(message.text()).toBe('click');
});
@ -540,7 +540,7 @@ test.describe('cli codegen', () => {
await recorder.setContentAndWait('<a target=_blank rel=noopener href="about:blank">link</a>');
const selector = await recorder.hoverOverElement('a');
expect(selector).toBe('text=link');
expect(selector).toBe('role=link[name=\"link\"]');
const [popup, sources] = await Promise.all([
page.context().waitForEvent('page'),
@ -551,28 +551,28 @@ test.describe('cli codegen', () => {
expect(sources.get('JavaScript').text).toContain(`
const [page1] = await Promise.all([
page.waitForEvent('popup'),
page.locator('text=link').click()
page.locator('role=link[name=\"link\"]').click()
]);`);
expect(sources.get('Java').text).toContain(`
Page page1 = page.waitForPopup(() -> {
page.locator("text=link").click();
page.locator("role=link[name=\\\"link\\\"]").click();
});`);
expect(sources.get('Python').text).toContain(`
with page.expect_popup() as popup_info:
page.locator(\"text=link\").click()
page.locator(\"role=link[name=\\\"link\\\"]\").click()
page1 = popup_info.value`);
expect(sources.get('Python Async').text).toContain(`
async with page.expect_popup() as popup_info:
await page.locator(\"text=link\").click()
await page.locator(\"role=link[name=\\\"link\\\"]\").click()
page1 = await popup_info.value`);
expect(sources.get('C#').text).toContain(`
var page1 = await page.RunAndWaitForPopupAsync(async () =>
{
await page.Locator(\"text=link\").ClickAsync();
await page.Locator(\"role=link[name=\\\"link\\\"]\").ClickAsync();
});`);
expect(popup.url()).toBe('about:blank');

View File

@ -231,28 +231,28 @@ test.describe('cli codegen', () => {
expect(sources.get('JavaScript').text).toContain(`
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator('text=Download').click()
page.locator('role=link[name=\"Download\"]').click()
]);`);
expect(sources.get('Java').text).toContain(`
BrowserContext context = browser.newContext();`);
expect(sources.get('Java').text).toContain(`
Download download = page.waitForDownload(() -> {
page.locator("text=Download").click();
page.locator("role=link[name=\\\"Download\\\"]").click();
});`);
expect(sources.get('Python').text).toContain(`
context = browser.new_context()`);
expect(sources.get('Python').text).toContain(`
with page.expect_download() as download_info:
page.locator(\"text=Download\").click()
page.locator(\"role=link[name=\\\"Download\\\"]\").click()
download = download_info.value`);
expect(sources.get('Python Async').text).toContain(`
context = await browser.new_context()`);
expect(sources.get('Python Async').text).toContain(`
async with page.expect_download() as download_info:
await page.locator(\"text=Download\").click()
await page.locator(\"role=link[name=\\\"Download\\\"]\").click()
download = await download_info.value`);
expect(sources.get('C#').text).toContain(`
@ -260,7 +260,7 @@ test.describe('cli codegen', () => {
expect(sources.get('C#').text).toContain(`
var download1 = await page.RunAndWaitForDownloadAsync(async () =>
{
await page.Locator(\"text=Download\").ClickAsync();
await page.Locator(\"role=link[name=\\\"Download\\\"]\").ClickAsync();
});`);
});
@ -283,22 +283,22 @@ test.describe('cli codegen', () => {
console.log(\`Dialog message: \${dialog.message()}\`);
dialog.dismiss().catch(() => {});
});
await page.locator('text=click me').click();`);
await page.locator('role=button[name=\"click me\"]').click();`);
expect(sources.get('Java').text).toContain(`
page.onceDialog(dialog -> {
System.out.println(String.format("Dialog message: %s", dialog.message()));
dialog.dismiss();
});
page.locator("text=click me").click();`);
page.locator("role=button[name=\\\"click me\\\"]").click();`);
expect(sources.get('Python').text).toContain(`
page.once(\"dialog\", lambda dialog: dialog.dismiss())
page.locator(\"text=click me\").click()`);
page.locator(\"role=button[name=\\\"click me\\\"]\").click()`);
expect(sources.get('Python Async').text).toContain(`
page.once(\"dialog\", lambda dialog: dialog.dismiss())
await page.locator(\"text=click me\").click()`);
await page.locator(\"role=button[name=\\\"click me\\\"]\").click()`);
expect(sources.get('C#').text).toContain(`
void page_Dialog1_EventHandler(object sender, IDialog dialog)
@ -308,7 +308,7 @@ test.describe('cli codegen', () => {
page.Dialog -= page_Dialog1_EventHandler;
}
page.Dialog += page_Dialog1_EventHandler;
await page.Locator(\"text=click me\").ClickAsync();`);
await page.Locator(\"role=button[name=\\\"click me\\\"]\").ClickAsync();`);
});
@ -333,7 +333,7 @@ test.describe('cli codegen', () => {
await recorder.setContentAndWait(`<a href="about:blank?foo">link</a>`);
const selector = await recorder.hoverOverElement('a');
expect(selector).toBe('text=link');
expect(selector).toBe('role=link[name=\"link\"]');
await page.click('a', { modifiers: [platform === 'darwin' ? 'Meta' : 'Control'] });
const sources = await recorder.waitForOutput('JavaScript', 'page1');
@ -352,7 +352,7 @@ test.describe('cli codegen', () => {
expect(sources.get('JavaScript').text).toContain(`
const [page1] = await Promise.all([
page.waitForEvent('popup'),
page.locator('text=link').click({
page.locator('role=link[name=\"link\"]').click({
modifiers: ['${platform === 'darwin' ? 'Meta' : 'Control'}']
})
]);`);

View File

@ -28,7 +28,7 @@ test.describe('cli codegen', () => {
`);
const selector = await recorder.hoverOverElement('button');
expect(selector).toBe('text=Submit >> nth=0');
expect(selector).toBe('role=button[name=\"Submit\"] >> nth=0');
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
@ -37,19 +37,19 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('text=Submit').first().click();`);
await page.locator('role=button[name=\"Submit\"]').first().click();`);
expect(sources.get('Python').text).toContain(`
page.locator("text=Submit").first.click()`);
page.locator("role=button[name=\\\"Submit\\\"]").first.click()`);
expect(sources.get('Python Async').text).toContain(`
await page.locator("text=Submit").first.click()`);
await page.locator("role=button[name=\\\"Submit\\\"]").first.click()`);
expect(sources.get('Java').text).toContain(`
page.locator("text=Submit").first().click();`);
page.locator("role=button[name=\\\"Submit\\\"]").first().click();`);
expect(sources.get('C#').text).toContain(`
await page.Locator("text=Submit").First.ClickAsync();`);
await page.Locator("role=button[name=\\\"Submit\\\"]").First.ClickAsync();`);
expect(message.text()).toBe('click1');
});
@ -63,7 +63,7 @@ test.describe('cli codegen', () => {
`);
const selector = await recorder.hoverOverElement('button >> nth=1');
expect(selector).toBe('text=Submit >> nth=1');
expect(selector).toBe('role=button[name=\"Submit\"] >> nth=1');
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
@ -72,19 +72,19 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('text=Submit').nth(1).click();`);
await page.locator('role=button[name=\"Submit\"]').nth(1).click();`);
expect(sources.get('Python').text).toContain(`
page.locator("text=Submit").nth(1).click()`);
page.locator("role=button[name=\\\"Submit\\\"]").nth(1).click()`);
expect(sources.get('Python Async').text).toContain(`
await page.locator("text=Submit").nth(1).click()`);
await page.locator("role=button[name=\\\"Submit\\\"]").nth(1).click()`);
expect(sources.get('Java').text).toContain(`
page.locator("text=Submit").nth(1).click();`);
page.locator("role=button[name=\\\"Submit\\\"]").nth(1).click();`);
expect(sources.get('C#').text).toContain(`
await page.Locator("text=Submit").Nth(1).ClickAsync();`);
await page.Locator("role=button[name=\\\"Submit\\\"]").Nth(1).ClickAsync();`);
expect(message.text()).toBe('click2');
});

View File

@ -35,7 +35,7 @@ it.describe('selector generator', () => {
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('div[role="button"]');
expect(await generate(page, 'div')).toBe('role=button');
});
it('should generate text and normalize whitespace', async ({ page }) => {
@ -43,14 +43,14 @@ it.describe('selector generator', () => {
expect(await generate(page, 'div')).toBe('text=Text some more text');
});
it('should not escape spaces inside attribute selectors', async ({ page }) => {
it('should not escape spaces inside named attr selectors', async ({ page }) => {
await page.setContent(`<input placeholder="Foo b ar"/>`);
expect(await generate(page, 'input')).toBe('[placeholder="Foo b ar"]');
expect(await generate(page, 'input')).toBe('attr=[placeholder=\"Foo b ar\"]');
});
it('should generate text for <input type=button>', async ({ page }) => {
await page.setContent(`<input type=button value="Click me">`);
expect(await generate(page, 'input')).toBe('text=Click me');
expect(await generate(page, 'input')).toBe('role=button[name=\"Click me\"]');
});
it('should trim text', async ({ page }) => {
@ -88,7 +88,7 @@ it.describe('selector generator', () => {
it('should prefer data-testid', async ({ page }) => {
await page.setContent(`<div>Text</div><div>Text</div><div data-testid=a>Text</div><div>Text</div>`);
expect(await generate(page, '[data-testid="a"]')).toBe('[data-testid="a"]');
expect(await generate(page, '[data-testid="a"]')).toBe('attr=[data-testid=\"a\"]');
});
it('should handle first non-unique data-testid', async ({ page }) => {
@ -99,7 +99,7 @@ it.describe('selector generator', () => {
<div data-testid=a>
Text
</div>`);
expect(await generate(page, 'div[mark="1"]')).toBe('[data-testid="a"] >> nth=0');
expect(await generate(page, 'div[mark="1"]')).toBe('attr=[data-testid=\"a\"] >> nth=0');
});
it('should handle second non-unique data-testid', async ({ page }) => {
@ -110,7 +110,7 @@ it.describe('selector generator', () => {
<div data-testid=a mark=1>
Text
</div>`);
expect(await generate(page, 'div[mark="1"]')).toBe(`[data-testid="a"] >> nth=1`);
expect(await generate(page, 'div[mark="1"]')).toBe(`attr=[data-testid=\"a\"] >> nth=1`);
});
it('should use readable id', async ({ page }) => {
@ -133,16 +133,17 @@ it.describe('selector generator', () => {
await page.setContent(`
<div>Hello world</div>
<a>Hello <span>world</span></a>
<a>Goodbye <span>world</span></a>
`);
expect(await generate(page, 'a')).toBe(`a:has-text("Hello world")`);
expect(await generate(page, 'a:has-text("Hello")')).toBe(`a:has-text("Hello world")`);
});
it('should chain text after parent', async ({ page }) => {
await page.setContent(`
<div>Hello <span>world</span></div>
<a>Hello <span mark=1>world</span></a>
<b>Hello <span mark=1>world</span></b>
`);
expect(await generate(page, '[mark="1"]')).toBe(`a >> text=world`);
expect(await generate(page, '[mark="1"]')).toBe(`b:has-text(\"Hello world\") span`);
});
it('should use parent text', async ({ page }) => {
@ -150,7 +151,7 @@ it.describe('selector generator', () => {
<div>Hello <span>world</span></div>
<div>Goodbye <span mark=1>world</span></div>
`);
expect(await generate(page, '[mark="1"]')).toBe(`text=Goodbye world >> span`);
expect(await generate(page, '[mark="1"]')).toBe(`div:has-text(\"Goodbye world\") span`);
});
it('should separate selectors by >>', async ({ page }) => {
@ -179,8 +180,8 @@ it.describe('selector generator', () => {
it('should use nested ordinals', async ({ page }) => {
await page.setContent(`
<a><c></c><c></c><c></c><c></c><c></c><b></b></a>
<a>
<div><c></c><c></c><c></c><c></c><c></c><b></b></div>
<div>
<b>
<c>
</c>
@ -188,16 +189,16 @@ it.describe('selector generator', () => {
<b>
<c mark=1></c>
</b>
</a>
<a><b></b></a>
</div>
<div><b></b></div>
`);
expect(await generate(page, 'c[mark="1"]')).toBe('b:nth-child(2) > c');
});
it('should properly join child selectors under nested ordinals', async ({ page }) => {
await page.setContent(`
<a><c></c><c></c><c></c><c></c><c></c><b></b></a>
<a>
<div><c></c><c></c><c></c><c></c><c></c><b></b></div>
<div>
<b>
<div>
<c>
@ -209,8 +210,8 @@ it.describe('selector generator', () => {
<c mark=1></c>
</div>
</b>
</a>
<a><b></b></a>
</div>
<div><b></b></div>
`);
expect(await generate(page, 'c[mark="1"]')).toBe('b:nth-child(2) > div > c');
});
@ -231,7 +232,7 @@ it.describe('selector generator', () => {
});
it('placeholder', async ({ page }) => {
await page.setContent(`<input placeholder="foobar" type="text"/>`);
expect(await generate(page, 'input')).toBe('[placeholder="foobar"]');
expect(await generate(page, 'input')).toBe('attr=[placeholder=\"foobar\"]');
});
it('type', async ({ page }) => {
await page.setContent(`<input type="text"/>`);
@ -316,9 +317,9 @@ it.describe('selector generator', () => {
await page.setContent(`<ng:switch><span></span></ng:switch>`);
expect(await generate(page, 'ng\\:switch')).toBe('ng\\:switch');
await page.setContent(`<div><span></span></div>`);
await page.$eval('div', div => div.setAttribute('aria-label', `!#'!?:`));
expect(await generate(page, 'div')).toBe("[aria-label=\"\\!\\#\\'\\!\\?\\:\"]");
await page.setContent(`<button><span></span></button><button></button>`);
await page.$eval('button', button => button.setAttribute('aria-label', `!#'!?:`));
expect(await generate(page, 'button')).toBe("role=button[name=\"\\!\\#\\'\\!\\?\\:\"]");
await page.setContent(`<div><span></span></div>`);
await page.$eval('div', div => div.id = `!#'!?:`);
@ -341,7 +342,7 @@ it.describe('selector generator', () => {
it('should accept valid aria-label for candidate consideration', async ({ page }) => {
await page.setContent(`<button aria-label="ariaLabel" id="buttonId"></button>`);
expect(await generate(page, 'button')).toBe('[aria-label="ariaLabel"]');
expect(await generate(page, 'button')).toBe('role=button[name=\"ariaLabel\"]');
});
it('should ignore empty role for candidate consideration', async ({ page }) => {
@ -349,9 +350,9 @@ it.describe('selector generator', () => {
expect(await generate(page, 'button')).toBe('#buttonId');
});
it('should accept valid role for candidate consideration', async ({ page }) => {
it('should not accept invalid role for candidate consideration', async ({ page }) => {
await page.setContent(`<button role="roleDescription" id="buttonId"></button>`);
expect(await generate(page, 'button')).toBe('button[role="roleDescription"]');
expect(await generate(page, 'button')).toBe('#buttonId');
});
it('should ignore empty data-test-id for candidate consideration', async ({ page }) => {