diff --git a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts index e0c10b53fd..9bae0a62bd 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts @@ -91,7 +91,8 @@ function parseLocator(locator: string, testIdAttributeName: string): { selector: .replace(/newregex\(([^)]+)\)/g, 'r$1') .replace(/string=/g, '=') .replace(/regex=/g, '=') - .replace(/,,/g, ','); + .replace(/,,/g, ',') + .replace(/,\)/g, ')'); const preferredQuote = params.map(p => p.quote).filter(quote => '\'"`'.includes(quote))[0] as Quote | undefined; return { selector: transform(template, params, testIdAttributeName), preferredQuote }; @@ -174,6 +175,7 @@ function transform(template: string, params: TemplateParams, testIdAttributeName .replace(/filter\(,?hasnot2=([^)]+)\)/g, 'internal:has-not=$1') .replace(/,exact=false/g, '') .replace(/,exact=true/g, 's') + .replace(/,includehidden=/g, ',include-hidden=') .replace(/\,/g, ']['); const parts = template.split('.'); @@ -233,6 +235,6 @@ export function locatorOrSelectorAsSelector(language: Language, locator: string, function digestForComparison(language: Language, locator: string) { locator = locator.replace(/\s/g, ''); if (language === 'javascript') - locator = locator.replace(/\\?["`]/g, '\''); + locator = locator.replace(/\\?["`]/g, '\'').replace(/,{}/g, ''); return locator; } diff --git a/tests/library/locator-generator.spec.ts b/tests/library/locator-generator.spec.ts index 4df72977f4..cceff133d4 100644 --- a/tests/library/locator-generator.spec.ts +++ b/tests/library/locator-generator.spec.ts @@ -196,6 +196,12 @@ it('reverse engineer getByRole', async ({ page }) => { java: `getByRole(AriaRole.BUTTON)`, csharp: `GetByRole(AriaRole.Button)`, }); + expect.soft(generate(page.getByRole('heading', {}))).toEqual({ + javascript: "getByRole('heading')", + python: 'get_by_role("heading")', + java: 'getByRole(AriaRole.HEADING)', + csharp: 'GetByRole(AriaRole.Heading)' + }); expect.soft(generate(page.getByRole('button', { name: 'Hello' }))).toEqual({ javascript: `getByRole('button', { name: 'Hello' })`, python: `get_by_role("button", name="Hello")`, @@ -559,6 +565,12 @@ it('parseLocator css', async () => { expect.soft(parseLocator('csharp', `Locator("css=.foo")`, '')).toBe(`css=.foo`); }); + +it('parseLocator options', async () => { + expect.soft(parseLocator('javascript', `getByRole('heading', {})`, '')).toBe(`internal:role=heading`); + expect.soft(parseLocator('javascript', `getByRole('checkbox', { checked:false, includeHidden: true })`, '')).toBe(`internal:role=checkbox[checked=false][include-hidden=true]`); +}); + it('parse locators strictly', () => { const selector = 'div >> internal:has-text=\"Goodbye world\"i >> span';