From d023829dd543c0ced528a60a0365327c39c6e419 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 16 Jan 2024 16:06:02 -0800 Subject: [PATCH] fix(locator parser): allow escaped quotes in the digest function (#29012) This supports mixed quotes locators in JavaScript where we are not sure what quote is the correct one, so we normalize to unescaped single quote when comparing with the original. Drive-by: we were allowing single quotes in Python, Java and .NET, but these are actually not allowed. Regressed in #27718. Fixes #28630. --- .../src/utils/isomorphic/locatorParser.ts | 11 +++++++---- tests/library/locator-generator.spec.ts | 9 ++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts index 886574e58001d..d9bbca2f7e0af 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts @@ -220,14 +220,17 @@ export function locatorOrSelectorAsSelector(language: Language, locator: string, try { const { selector, preferredQuote } = parseLocator(locator, testIdAttributeName); const locators = asLocators(language, selector, undefined, undefined, preferredQuote); - const digest = digestForComparison(locator); - if (locators.some(candidate => digestForComparison(candidate) === digest)) + const digest = digestForComparison(language, locator); + if (locators.some(candidate => digestForComparison(language, candidate) === digest)) return selector; } catch (e) { } return ''; } -function digestForComparison(locator: string) { - return locator.replace(/\s/g, '').replace(/["`]/g, '\''); +function digestForComparison(language: Language, locator: string) { + locator = locator.replace(/\s/g, ''); + if (language === 'javascript') + locator = locator.replace(/\\?["`]/g, '\''); + return locator; } diff --git a/tests/library/locator-generator.spec.ts b/tests/library/locator-generator.spec.ts index a5731214deab0..30ee22ef0e34e 100644 --- a/tests/library/locator-generator.spec.ts +++ b/tests/library/locator-generator.spec.ts @@ -539,6 +539,13 @@ it('parseLocator quotes', async () => { expect.soft(parseLocator('java', `locator('text="bar"')`, '')).toBe(``); expect.soft(parseLocator('csharp', `Locator("text='bar'")`, '')).toBe(`text='bar'`); expect.soft(parseLocator('csharp', `Locator('text="bar"')`, '')).toBe(``); + + const mixedQuotes = ` + locator("[id*=freetext-field]") + .locator('input:below(:text("Assigned Number:"))') + .locator("visible=true") + `; + expect.soft(parseLocator('javascript', mixedQuotes, '')).toBe(`[id*=freetext-field] >> input:below(:text("Assigned Number:")) >> visible=true`); }); it('parseLocator css', async () => { @@ -563,7 +570,7 @@ it('parse locators strictly', () => { // Quotes expect.soft(parseLocator('javascript', `locator("div").filter({ hasText: "Goodbye world" }).locator("span")`)).toBe(selector); - expect.soft(parseLocator('python', `locator('div').filter(has_text='Goodbye world').locator('span')`)).toBe(selector); + expect.soft(parseLocator('python', `locator('div').filter(has_text='Goodbye world').locator('span')`)).not.toBe(selector); // Whitespace expect.soft(parseLocator('csharp', `Locator("div") . Filter (new ( ) { HasText = "Goodbye world" }).Locator( "span" )`)).toBe(selector);