title: In puppeteer, how I get content of nextSibling of element found by innerText?
body: <p>I have following html:</p>
  <pre class="lang-html prettyprint-override"><code>&lt;article data-tid=&quot;product-detail&quot;&gt; &lt;!-- I page.$eval on this element. --&gt;
    &lt;h1 itemprop=&quot;name&quot;&gt;Product name&lt;/h1&gt; &lt;!-- This I can query by itemprop --&gt;
    
    &lt;h2&gt;Some other topic&lt;/h2&gt;
    &lt;p&gt;I don't want this text.&lt;/p&gt;
  
    &lt;h2&gt;Unique topic&lt;/h2&gt; &lt;!-- I can find this innerText === &quot;Unique topic&quot;. --&gt;
    &lt;p&gt;Text I want real' bad.&lt;/p&gt; &lt;!-- I want this innerText. --&gt;
  
    &lt;h2&gt;Some other topic&lt;/h2&gt;
    &lt;p&gt;I don't want this text.&lt;/p&gt;
  &lt;/article&gt;
  </code></pre>
  <p>How do I get the &quot;Text I want real' bad.&quot; from the page, knowing the &quot;Unique topic&quot;?</p>
  <p>I run puppeteer from node script.</p>
  <hr />
  <p>This is what I have so far:</p>
  <pre class="lang-js prettyprint-override"><code>async function puppeteerProductDataExtractor($product) {
    // This works like charm. 
    const productName = $product.querySelector('[itemprop=name]')?.innerText;
  
    // Now I have to find the right h2 and get it's next element sibling. 
    const h2 = $product.querySelectorAll('h2');
    
    // 1. If I try to get innerText of all h2, it fails with getProperty function not defined.
    console.log(
      await Promise.all([...h2].map(async $el =&gt; await (await element.getProperty('innerText')).jsonValue()))
    );
  
    // 2. This returns empty array
    console.log([...h2].filter($el =&gt; $el.innerText.startsWith('Unique topic')));
    // This prints JSHandle@array - innerText is not a string. 
    console.log([...h2].map($el =&gt; $el.innerText));
    // This also prints JSHandle@array which is just insane.
    console.log([...h2].map($el =&gt; Object.keys($el)));
    // This fails with &quot;property is not a function&quot; error. 
    console.log([...h2].map(el =&gt; el.property('innerText')));
    // So does this. 
    console.log([...h2].map(el =&gt; el.getProperty('innerText')));
  }
  
  page.on('console', consoleObj =&gt; console.log('xxxx', consoleObj.text()));
  
  const product = await page.$eval('article[data-tid=product-detail]', puppeteerProductDataExtractor);
  </code></pre>
  <p>The first attempt comes from here: <a href="https://stackoverflow.com/a/52828950/336753">https://stackoverflow.com/a/52828950/336753</a></p>
  <p>Everything else is just frustrated blind shooting. Must admit I'm very confused. Some of the stuff should work according to docs, but it just fails. Like JSHandle <a href="https://pub.dev/documentation/puppeteer/latest/puppeteer/JsHandle-class.html" rel="nofollow noreferrer">should have the <code>property</code> function</a>, but it fails, when I call it (not a function).</p>
  <p>I didn't even get to the <code>nextSibling</code> part.</p>
  <p>I tried a lot of code which mostly failed and don't want to pollute the question with it. It feels this should be really simple and I'm just missing something. Hope the original intention is clear.</p>
  <p>I'm sure there is a simple solution, but trial-and-failure seems not to be the way to get to it.</p>
  <hr />
  <p>After more digging, turns out my original intention was correct. The <code>filter()</code> didn't work not because innerText would be instance of JSHandle (as it appears), but because uppercase first letter was done by CSS (had to lowercase to unify the string before comparison). Kinda ashamed here... Sorry and thanks @ggorlen for assistance.</p>
  <pre class="lang-js prettyprint-override"><code>/* WE'RE INSIDE $eval FUNCTION */
  // This returns JSHandle@array which is just weird...
  console.log([...h2].map(el =&gt; el.innerText));
  // But this returns the joined string correctly. Huh... 
  console.log([...h2].map(el =&gt; el.innerText).join(';'));
  // So this eventually works
  console.log([...h2]
    .filter(el =&gt; el.textContent.toLowerCase().startsWith('unique topic'))[0]?
    .nextElementSibling.textContent);
  </code></pre>