diff --git a/packages/playwright-core/src/server/injected/selectorGenerator.ts b/packages/playwright-core/src/server/injected/selectorGenerator.ts index c091256814..7f3e000cef 100644 --- a/packages/playwright-core/src/server/injected/selectorGenerator.ts +++ b/packages/playwright-core/src/server/injected/selectorGenerator.ts @@ -81,7 +81,7 @@ function generateSelectorFor(injectedScript: InjectedScript, targetElement: Elem // Do not use regex for parent elements (for performance). textCandidates = filterRegexTokens(textCandidates); } - const noTextCandidates = buildCandidates(element, accessibleNameCache).map(token => [token]); + const noTextCandidates = buildCandidates(injectedScript, 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); @@ -144,7 +144,7 @@ function generateSelectorFor(injectedScript: InjectedScript, targetElement: Elem return calculateCached(targetElement, true); } -function buildCandidates(element: Element, accessibleNameCache: Map): SelectorToken[] { +function buildCandidates(injectedScript: InjectedScript, element: Element, accessibleNameCache: Map): SelectorToken[] { const candidates: SelectorToken[] = []; if (element.getAttribute('data-testid')) @@ -155,10 +155,15 @@ function buildCandidates(element: Element, accessibleNameCache: Map = {}; diff --git a/packages/playwright-core/src/server/recorder/python.ts b/packages/playwright-core/src/server/recorder/python.ts index 631ce3a56d..b11b509f42 100644 --- a/packages/playwright-core/src/server/recorder/python.ts +++ b/packages/playwright-core/src/server/recorder/python.ts @@ -258,6 +258,11 @@ with sync_playwright() as playwright: } function toCallWithExact(method: string, body: string, exact: boolean) { + if (body.startsWith('/') && (body.endsWith('/') || body.endsWith('/i'))) { + const regex = body.substring(1, body.lastIndexOf('/')); + const suffix = body.endsWith('i') ? ', re.IGNORECASE' : ''; + return `${method}(re.compile(r${quote(regex)}${suffix}))`; + } if (exact) return `${method}(${quote(body)}, exact=true)`; return `${method}(${quote(body)})`; diff --git a/tests/library/inspector/cli-codegen-3.spec.ts b/tests/library/inspector/cli-codegen-3.spec.ts index 52421de213..adbe18707e 100644 --- a/tests/library/inspector/cli-codegen-3.spec.ts +++ b/tests/library/inspector/cli-codegen-3.spec.ts @@ -319,4 +319,61 @@ test.describe('cli codegen', () => { await page.GetByAltText("Country").ClickAsync();`); }); + test('should generate getByLabel', async ({ page, openRecorder }) => { + const recorder = await openRecorder(); + + await recorder.setContentAndWait(``); + + const selector = await recorder.hoverOverElement('input'); + expect(selector).toBe('internal:label=Country'); + + const [sources] = await Promise.all([ + recorder.waitForOutput('JavaScript', 'click'), + page.dispatchEvent('input', 'click', { detail: 1 }) + ]); + + expect.soft(sources.get('JavaScript').text).toContain(` + await page.getByLabel('Country').click();`); + + expect.soft(sources.get('Python').text).toContain(` + page.get_by_label("Country").click()`); + + expect.soft(sources.get('Python Async').text).toContain(` + await page.get_by_label("Country").click()`); + + expect.soft(sources.get('Java').text).toContain(` + page.getByLabel("Country").click()`); + + expect.soft(sources.get('C#').text).toContain(` + await page.GetByLabel("Country").ClickAsync();`); + }); + + test('should generate getByLabel with regex', async ({ page, openRecorder }) => { + const recorder = await openRecorder(); + + await recorder.setContentAndWait(``); + + const selector = await recorder.hoverOverElement('input'); + expect(selector).toBe('internal:label=/Coun"try/'); + + const [sources] = await Promise.all([ + recorder.waitForOutput('JavaScript', 'click'), + page.dispatchEvent('input', 'click', { detail: 1 }) + ]); + + expect.soft(sources.get('JavaScript').text).toContain(` + await page.getByLabel(/Coun"try/).click();`); + + expect.soft(sources.get('Python').text).toContain(` + page.get_by_label(re.compile(r"Coun\\\"try")).click()`); + + expect.soft(sources.get('Python Async').text).toContain(` + await page.get_by_label(re.compile(r"Coun\\\"try")).click()`); + + expect.soft(sources.get('Java').text).toContain(` + page.getByLabel(Pattern.compile("Coun\\\"try")).click()`); + + expect.soft(sources.get('C#').text).toContain(` + await page.GetByLabel(new Regex("Coun\\\"try")).ClickAsync();`); + }); }); diff --git a/tests/library/selector-generator.spec.ts b/tests/library/selector-generator.spec.ts index e18138041c..95742c1215 100644 --- a/tests/library/selector-generator.spec.ts +++ b/tests/library/selector-generator.spec.ts @@ -366,4 +366,11 @@ it.describe('selector generator', () => { expect(await generate(page, 'button')).toBe('[data-test-id="testId"]'); }); + it('should generate label selector', async ({ page }) => { + await page.setContent(``); + expect(await generate(page, 'input')).toBe('internal:label=Country'); + + await page.setContent(``); + expect(await generate(page, 'input')).toBe('internal:label=/Coun"try/'); + }); });