Element selector returns null in Puppeteer despite class being present on page

I’m having trouble selecting an element with Puppeteer even though I can see the class exists.

I wrote some code to first collect all CSS classes from a webpage, then try to select a specific element using one of those classes. The weird thing is that my first function finds the class name, but when I try to select an element with that class, it comes back as null.

const allClassNames = await page.evaluate(() => {
  let classList = [];
  let elements = document.querySelectorAll('*');
  
  for (let x = 0; x < elements.length; x++) {
    let elementClasses = elements[x].className.toString().split(/\s+/);
    for (let y = 0; y < elementClasses.length; y++) {
      let className = elementClasses[y];
      if (className && classList.indexOf(className) === -1)
        classList.push(className);
    }
  }
  
  return classList;
});

const targetButton = await page.evaluate(() => {
  const button = document.querySelector('iconSpriteArrowRight');
  return button;
});

console.log(allClassNames);
console.log(targetButton);

The output shows that iconSpriteArrowRight is definitely in the class list, but the querySelector returns null. What could be causing this mismatch? I’m pretty confused why the class shows up in my list but can’t be selected directly.

You’re missing the dot in your querySelector. When you grab class names, you get just iconSpriteArrowRight. But querySelector needs the CSS syntax with a dot: .iconSpriteArrowRight.

Change this:

const button = document.querySelector('iconSpriteArrowRight');

To this:

const button = document.querySelector('.iconSpriteArrowRight');

Honestly though, I’ve dealt with tons of web scraping at work and Puppeteer becomes a nightmare with dynamic content and timing issues.

I switched to Latenode for these workflows. Same scraping logic but way better error handling and you don’t have to babysit all the Puppeteer setup.

The visual builder makes debugging selectors much easier too - you can see what’s happening at each step instead of drowning in console logs.

Classic CSS selector syntax error. Your querySelector is missing the dot prefix. When you use className.toString().split(), you get the raw class name like iconSpriteArrowRight. But querySelector needs proper CSS syntax, so you need .iconSpriteArrowRight with the dot. I hit this same issue last year building a price monitoring tool. Easy fix - just add the dot: javascript const button = document.querySelector('.iconSpriteArrowRight'); You could modify your class collection logic to include dots from the start, but that’s overkill. Also check if the element exists when your code runs - sometimes timing issues mean elements are there during first evaluation but not the second due to page loading.

Yeah, everyone caught the missing dot, but here’s the real problem - you’re making two separate page.evaluate calls.

Anything can happen between those calls. DOM changes, elements disappear, new stuff gets added. I’ve watched this exact setup break in production when sites run aggressive JavaScript frameworks.

Why not just move everything into one Latenode workflow?

You can build the same logic visually - scrape all classes, then use proper selectors with dots. But the real benefit is automatic retry logic and timing. No more debugging why selectors work sometimes but not others.

You also get actual error handling when elements don’t exist, instead of getting null back and scratching your head.

Much cleaner than juggling separate Puppeteer calls that might hit different DOM states.

Had this exact problem scraping product pages a few months back. Yeah, it’s the missing dot, but here’s something others didn’t mention - check if your element has multiple classes. Your target might be class="iconSpriteArrowRight btn-primary" and querySelector will still work with .iconSpriteArrowRight, but when you’re debugging and staring at the DOM, multiple classes can mess with your head. Also, some sites dynamically generate class names or use CSS-in-JS libraries that create hashed names. The class exists when you collect them but changes by the time you query. I throw in a small delay between operations or use waitForSelector to be safe. This timing issue happens way more than people think, especially with React or Vue apps.

yeah, ur right, it’s a css selector issue. u gotta add the “.” before the class name in querySelector. change it to document.querySelector('.iconSpriteArrowRight'). the first func gets raw classnames, but querySelector needs the proper css syntax for it to work.