diff --git a/src/injected/textSelectorEngine.ts b/src/injected/textSelectorEngine.ts index ae29a98261..b94e28205b 100644 --- a/src/injected/textSelectorEngine.ts +++ b/src/injected/textSelectorEngine.ts @@ -86,21 +86,33 @@ function queryInternal(root: SelectorRoot, matcher: Matcher, shadow: boolean): E const shadowRoots: ShadowRoot[] = []; if (shadow && (root as Element).shadowRoot) shadowRoots.push((root as Element).shadowRoot!); - while (walker.nextNode()) { - const node = walker.currentNode; - if (node.nodeType === Node.ELEMENT_NODE) { + + let lastTextParent: Element | null = null; + let lastText = ''; + while (true) { + const node = walker.nextNode(); + + const textParent = (node && node.nodeType === Node.TEXT_NODE) ? node.parentElement : null; + if (lastTextParent && textParent !== lastTextParent) { + if (lastTextParent.nodeName !== 'SCRIPT' && lastTextParent.nodeName !== 'STYLE' && matcher(lastText)) + return lastTextParent; + lastText = ''; + } + lastTextParent = textParent; + + if (!node) + break; + if (node.nodeType === Node.TEXT_NODE) { + lastText += node.nodeValue; + } else { const element = node as Element; if ((element instanceof HTMLInputElement) && (element.type === 'submit' || element.type === 'button') && matcher(element.value)) return element; if (shadow && element.shadowRoot) shadowRoots.push(element.shadowRoot); - } else { - const element = node.parentElement; - const text = node.nodeValue; - if (element && element.nodeName !== 'SCRIPT' && element.nodeName !== 'STYLE' && text && matcher(text)) - return element; } } + for (const shadowRoot of shadowRoots) { const element = queryInternal(shadowRoot, matcher, shadow); if (element) @@ -114,21 +126,33 @@ function queryAllInternal(root: SelectorRoot, matcher: Matcher, shadow: boolean, const shadowRoots: ShadowRoot[] = []; if (shadow && (root as Element).shadowRoot) shadowRoots.push((root as Element).shadowRoot!); - while (walker.nextNode()) { - const node = walker.currentNode; - if (node.nodeType === Node.ELEMENT_NODE) { + + let lastTextParent: Element | null = null; + let lastText = ''; + while (true) { + const node = walker.nextNode(); + + const textParent = (node && node.nodeType === Node.TEXT_NODE) ? node.parentElement : null; + if (lastTextParent && textParent !== lastTextParent) { + if (lastTextParent.nodeName !== 'SCRIPT' && lastTextParent.nodeName !== 'STYLE' && matcher(lastText)) + result.push(lastTextParent); + lastText = ''; + } + lastTextParent = textParent; + + if (!node) + break; + if (node.nodeType === Node.TEXT_NODE) { + lastText += node.nodeValue; + } else { const element = node as Element; if ((element instanceof HTMLInputElement) && (element.type === 'submit' || element.type === 'button') && matcher(element.value)) result.push(element); if (shadow && element.shadowRoot) shadowRoots.push(element.shadowRoot); - } else { - const element = node.parentElement; - const text = node.nodeValue; - if (element && element.nodeName !== 'SCRIPT' && element.nodeName !== 'STYLE' && text && matcher(text)) - result.push(element); } } + for (const shadowRoot of shadowRoots) queryAllInternal(shadowRoot, matcher, shadow, result); } diff --git a/test/queryselector.spec.js b/test/queryselector.spec.js index 70f25facd0..0c39efc50a 100644 --- a/test/queryselector.spec.js +++ b/test/queryselector.spec.js @@ -540,6 +540,26 @@ describe('text selector', () => { await page.setContent(`
'
"
`); expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('
"
'); expect(await page.$eval(`text='`, 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
'); + expect(await page.$(`text=ab`)).toBe(null); + expect(await page.$$eval(`text=a`, els => els.length)).toBe(2); + expect(await page.$$eval(`text=b`, els => els.length)).toBe(1); + expect(await page.$$eval(`text=ab`, els => els.length)).toBe(0); + + await page.setContent(`
`); + await page.$eval('div', div => { + div.appendChild(document.createTextNode('hello')); + div.appendChild(document.createTextNode('world')); + }); + await page.$eval('span', span => { + span.appendChild(document.createTextNode('hello')); + span.appendChild(document.createTextNode('world')); + }); + expect(await page.$eval(`text=lowo`, e => e.outerHTML)).toBe('
helloworld
'); + expect(await page.$$eval(`text=lowo`, els => els.map(e => e.outerHTML).join(''))).toBe('
helloworld
helloworld'); }); it('create', async ({page}) => {