diff --git a/packages/playwright-core/src/server/common/selectorParser.ts b/packages/playwright-core/src/server/common/selectorParser.ts index 9ded595647..7e3de28b4f 100644 --- a/packages/playwright-core/src/server/common/selectorParser.ts +++ b/packages/playwright-core/src/server/common/selectorParser.ts @@ -173,6 +173,13 @@ function parseSelectorString(selector: string): ParsedSelectorStrings { return result; } + const shouldIgnoreTextSelectorQuote = () => { + const prefix = selector.substring(start, index); + const match = prefix.match(/^\s*text\s*=(.*)$/); + // Must be a text selector with some text before the quote. + return !!match && !!match[1]; + }; + while (index < selector.length) { const c = selector[index]; if (c === '\\' && index + 1 < selector.length) { @@ -180,7 +187,7 @@ function parseSelectorString(selector: string): ParsedSelectorStrings { } else if (c === quote) { quote = undefined; index++; - } else if (!quote && (c === '"' || c === '\'' || c === '`')) { + } else if (!quote && (c === '"' || c === '\'' || c === '`') && !shouldIgnoreTextSelectorQuote()) { quote = c; index++; } else if (!quote && c === '>' && selector[index + 1] === '>') { diff --git a/tests/page/selectors-text.spec.ts b/tests/page/selectors-text.spec.ts index f837b7b1ef..5975e5cdc2 100644 --- a/tests/page/selectors-text.spec.ts +++ b/tests/page/selectors-text.spec.ts @@ -303,6 +303,7 @@ it('should be case sensitive if quotes are specified', async ({ page }) => { await page.setContent(`
yo
ya
\nye
`); expect(await page.$eval(`text=yA`, e => e.outerHTML)).toBe('
ya
'); expect(await page.$(`text="yA"`)).toBe(null); + expect(await page.$(`text= "ya"`)).toBe(null); }); it('should search for a substring without quotes', async ({ page }) => { @@ -411,3 +412,30 @@ it('should work with leading and trailing spaces', async ({ page }) => { await expect(page.locator('text=Add widget')).toBeVisible(); await expect(page.locator('text= Add widget ')).toBeVisible(); }); + +it('should work with unpaired quotes when not at the start', async ({ page }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/12719' }); + await page.setContent(` +
hello"worldyay
+
hello'worldnay
+
hello\`worldoh
+
hello\`worldoh2
+ `); + expect(await page.$eval('text=lo" >> span', e => e.outerHTML)).toBe('yay'); + expect(await page.$eval(' text=lo" >> span', e => e.outerHTML)).toBe('yay'); + expect(await page.$eval('text =lo" >> span', e => e.outerHTML)).toBe('yay'); + expect(await page.$eval('text= lo" >> span', e => e.outerHTML)).toBe('yay'); + expect(await page.$eval(' text = lo" >> span', e => e.outerHTML)).toBe('yay'); + expect(await page.$eval('text=o"wor >> span', e => e.outerHTML)).toBe('yay'); + + expect(await page.$eval(`text=lo'wor >> span`, e => e.outerHTML)).toBe('nay'); + expect(await page.$eval(`text=o' >> span`, e => e.outerHTML)).toBe('nay'); + + expect(await page.$eval(`text=ello\`wor >> span`, e => e.outerHTML)).toBe('oh'); + await expect(page.locator(`text=ello\`wor`).locator('span').first()).toHaveText('oh'); + await expect(page.locator(`text=ello\`wor`).locator('span').nth(1)).toHaveText('oh2'); + + expect(await page.$(`text='wor >> span`)).toBe(null); + expect(await page.$(`text=" >> span`)).toBe(null); + expect(await page.$(`text=\` >> span`)).toBe(null); +});