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);
+});