From fdbd4fe197f4e7ac7e1d2a00df4414c40e765f60 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 28 May 2020 14:49:39 -0700 Subject: [PATCH] fix(selectors): fix selector parsing for css attributes and quotes (#2389) - css attribute selector may contain spaces; - >> escaping inside strings was sometimes incorrect. See added test cases for more details. --- src/injected/cssSelectorEngine.ts | 13 ++++++++++--- src/selectors.ts | 3 +++ test/queryselector.spec.js | 32 +++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/injected/cssSelectorEngine.ts b/src/injected/cssSelectorEngine.ts index c469fd0009..de173bfcf1 100644 --- a/src/injected/cssSelectorEngine.ts +++ b/src/injected/cssSelectorEngine.ts @@ -219,6 +219,7 @@ function parentElementOrShadowHost(element: Element): Element | undefined { function split(selector: string): string[][] { let index = 0; let quote: string | undefined; + let insideAttr = false; let start = 0; let space: 'none' | 'before' | 'after' = 'none'; const result: string[][] = []; @@ -235,7 +236,7 @@ function split(selector: string): string[][] { }; while (index < selector.length) { const c = selector[index]; - if (!quote && c === ' ') { + if (!quote && !insideAttr && c === ' ') { if (space === 'none' || space === 'before') space = 'before'; index++; @@ -256,10 +257,16 @@ function split(selector: string): string[][] { } else if (c === quote) { quote = undefined; index++; - } else if (c === '\'' || c === '"') { + } else if (!quote && (c === '\'' || c === '"')) { quote = c; index++; - } else if (!quote && c === ',') { + } else if (!quote && c === '[') { + insideAttr = true; + index++; + } else if (!quote && insideAttr && c === ']') { + insideAttr = false; + index++; + } else if (!quote && !insideAttr && c === ',') { appendToResult(); index++; start = index; diff --git a/src/selectors.ts b/src/selectors.ts index 97a99239d7..92891e9022 100644 --- a/src/selectors.ts +++ b/src/selectors.ts @@ -219,6 +219,9 @@ export function parseSelector(selector: string): types.ParsedSelector { } else if (c === quote) { quote = undefined; index++; + } else if (!quote && (c === '"' || c === '\'' || c === '`')) { + quote = c; + index++; } else if (!quote && c === '>' && selector[index + 1] === '>') { append(); index += 2; diff --git a/test/queryselector.spec.js b/test/queryselector.spec.js index b1fc7b04b2..29a96b0078 100644 --- a/test/queryselector.spec.js +++ b/test/queryselector.spec.js @@ -494,6 +494,14 @@ describe('text selector', () => { expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('
"
'); expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('
\'
'); + await page.setContent(`
Hi''>>foo=bar
`); + expect(await page.$eval(`text="Hi''>>foo=bar"`, e => e.outerHTML)).toBe(`
Hi''>>foo=bar
`); + await page.setContent(`
Hi'">>foo=bar
`); + expect(await page.$eval(`text="Hi'\\">>foo=bar"`, e => e.outerHTML)).toBe(`
Hi'">>foo=bar
`); + + await page.setContent(`
Hi>>
`); + expect(await page.$eval(`text="Hi>>">>span`, e => e.outerHTML)).toBe(``); + await page.setContent(`
a
b
a
`); expect(await page.$eval(`text=a`, e => e.outerHTML)).toBe('
a
b
'); expect(await page.$eval(`text=b`, e => e.outerHTML)).toBe('
a
b
'); @@ -622,6 +630,30 @@ describe('css selector', () => { expect(await page.$eval(`css=[attr='hello,world!']`, e => e.outerHTML)).toBe('
'); expect(await page.$eval(`css=div[attr="hello,world!"],span`, e => e.outerHTML)).toBe(''); }); + + it('should work with attribute selectors', async({page}) => { + await page.setContent(`
`); + await page.evaluate(() => window.div = document.querySelector('div')); + const selectors = [ + `[attr="hello world"]`, + `[attr = "hello world"]`, + `[attr ~= world]`, + `[attr ^=hello ]`, + `[attr $= world ]`, + `[attr *= "llo wor" ]`, + `[attr2 |= hello]`, + `[attr = "Hello World" i ]`, + `[attr *= "llo WOR"i]`, + `[attr $= woRLD i]`, + `[attr2 = "hello-''>>foo=bar[]"]`, + `[attr2 $="foo=bar[]"]`, + ]; + for (const selector of selectors) + expect(await page.$eval(selector, e => e === div)).toBe(true); + expect(await page.$eval(`[attr*=hello] span`, e => e.parentNode === div)).toBe(true); + expect(await page.$eval(`[attr*=hello] >> span`, e => e.parentNode === div)).toBe(true); + expect(await page.$eval(`[attr3="] span"] >> span`, e => e.parentNode === div)).toBe(true); + }); }); describe('attribute selector', () => {