mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-05 17:07:10 +03:00
chore(codegen): prioritize role selectors (#17750)
This commit is contained in:
parent
ff6d240e83
commit
42a4d8a829
@ -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;
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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'}']
|
||||
})
|
||||
]);`);
|
||||
|
@ -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');
|
||||
});
|
||||
|
@ -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 }) => {
|
||||
|
Loading…
Reference in New Issue
Block a user