From 42a4d8a8298b0b00092c055968743a825f1c337f Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 3 Oct 2022 07:44:24 -0800 Subject: [PATCH] chore(codegen): prioritize role selectors (#17750) --- .../src/server/injected/selectorGenerator.ts | 50 ++++++++++------- tests/library/inspector/cli-codegen-1.spec.ts | 40 +++++++------- tests/library/inspector/cli-codegen-2.spec.ts | 24 ++++----- tests/library/inspector/cli-codegen-3.spec.ts | 24 ++++----- tests/library/selector-generator.spec.ts | 53 ++++++++++--------- 5 files changed, 103 insertions(+), 88 deletions(-) diff --git a/packages/playwright-core/src/server/injected/selectorGenerator.ts b/packages/playwright-core/src/server/injected/selectorGenerator.ts index 9072c87075..5709791bf7 100644 --- a/packages/playwright-core/src/server/injected/selectorGenerator.ts +++ b/packages/playwright-core/src/server/injected/selectorGenerator.ts @@ -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): 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; } diff --git a/tests/library/inspector/cli-codegen-1.spec.ts b/tests/library/inspector/cli-codegen-1.spec.ts index 9f5cab42f8..9f9680fc4c 100644 --- a/tests/library/inspector/cli-codegen-1.spec.ts +++ b/tests/library/inspector/cli-codegen-1.spec.ts @@ -26,7 +26,7 @@ test.describe('cli codegen', () => { await recorder.setContentAndWait(``); 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', () => { `); 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('link'); 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'); diff --git a/tests/library/inspector/cli-codegen-2.spec.ts b/tests/library/inspector/cli-codegen-2.spec.ts index 4e51f8fb19..4ecc89c81f 100644 --- a/tests/library/inspector/cli-codegen-2.spec.ts +++ b/tests/library/inspector/cli-codegen-2.spec.ts @@ -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(`link`); 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'}'] }) ]);`); diff --git a/tests/library/inspector/cli-codegen-3.spec.ts b/tests/library/inspector/cli-codegen-3.spec.ts index 853fde6173..544c85e82d 100644 --- a/tests/library/inspector/cli-codegen-3.spec.ts +++ b/tests/library/inspector/cli-codegen-3.spec.ts @@ -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'); }); diff --git a/tests/library/selector-generator.spec.ts b/tests/library/selector-generator.spec.ts index a8da7250f6..87424d453d 100644 --- a/tests/library/selector-generator.spec.ts +++ b/tests/library/selector-generator.spec.ts @@ -35,7 +35,7 @@ it.describe('selector generator', () => { it('should prefer role=button over inner span', async ({ page }) => { await page.setContent(`
`); - 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(``); - 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 ', async ({ page }) => { await page.setContent(``); - 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(`
Text
Text
Text
Text
`); - 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', () => {
Text
`); - 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', () => {
Text
`); - 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(`
Hello world
Hello world + Goodbye world `); - 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(`
Hello world
- Hello world + Hello world `); - 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', () => {
Hello world
Goodbye world
`); - 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(` - - +
+
@@ -188,16 +189,16 @@ it.describe('selector generator', () => { - - +
+
`); 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(` - - +
+
@@ -209,8 +210,8 @@ it.describe('selector generator', () => {
-
- +
+
`); 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(``); - expect(await generate(page, 'input')).toBe('[placeholder="foobar"]'); + expect(await generate(page, 'input')).toBe('attr=[placeholder=\"foobar\"]'); }); it('type', async ({ page }) => { await page.setContent(``); @@ -316,9 +317,9 @@ it.describe('selector generator', () => { await page.setContent(``); expect(await generate(page, 'ng\\:switch')).toBe('ng\\:switch'); - await page.setContent(`
`); - await page.$eval('div', div => div.setAttribute('aria-label', `!#'!?:`)); - expect(await generate(page, 'div')).toBe("[aria-label=\"\\!\\#\\'\\!\\?\\:\"]"); + await page.setContent(``); + await page.$eval('button', button => button.setAttribute('aria-label', `!#'!?:`)); + expect(await generate(page, 'button')).toBe("role=button[name=\"\\!\\#\\'\\!\\?\\:\"]"); await page.setContent(`
`); 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(``); - 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(``); - 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 }) => {