1

I am trying to take screenshots of each section in a landing page which may container multiple sections. I was able to do that effectively in "Round1" which I commented out.

My goal is to learn how to write leaner/cleaner code so I made another attempt, "Round2".

In this section it does take a screenshot. But, it takes screenshot of section 3 with file name JSHandle@node.png. Definitely, I am doing this wrong.

Round1 (works perfectly)

const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://www.somelandingpage.com');

// const elOne = await page.$('.section-one');
// await elOne.screenshot({path: './public/SectionOne.png'}) 
// takes a screenshot SectionOne.png

// const elTwo = await page.$('.section-two')
// await elTwo.screenshot({path: './public/SectionTwo.png'})
// takes a screenshot SectionTwo.png

// const elThree = await page.$('.section-three')
// await elThree.screenshot({path: './public/SectionThree.png'})
// takes a screenshot SectionThree.png

Round2

I created an array that holds all the variables and tried to loop through them.

const elOne = await page.$('.section-one');
const elTwo = await page.$('.section-two')
const elThree = await page.$('.section-three')
    
    let lpElements = [elOne, elTwo, elThree];
        for(var i=0; i<lpElements.length; i++){

            await lpElements[i].screenshot({path: './public/'+lpElements[i] + '.png'})       
    }
await browser.close();
})();

This takes a screenshot of section-three only, but with wrong file name (JSHandle@node.png). There are no error messages on the console. How can I reproduce Round1 by modifying the Round2 code?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
NewCoder
  • 43
  • 8

1 Answers1

1

Your array is only of Puppeteer element handle objects which are getting .toString() called on them.

A clean way to do this is to use an array of objects, each of which has a selector and its name. Then, when you run your loop, you have access to both name and selector.

const puppeteer = require('puppeteer');

const content = `
  <div class="section-one">foo</div>
  <div class="section-two">bar</div>
  <div class="section-three">baz</div>
`;
const elementsToScreenshot = [
  {selector: '.section-one', name: 'SectionOne'},
  {selector: '.section-two', name: 'SectionTwo'},
  {selector: '.section-three', name: 'SectionThree'},
];
const getPath = name => `./public/${name}.png`;

let browser;
(async () => {
  browser = await puppeteer.launch();
  const [page] = await browser.pages();
  await page.setContent(content);

  for (const {selector, name} of elementsToScreenshot) {
    const el = await page.$(selector);
    await el.screenshot({path: getPath(name)});
  }
})()
  .catch(err => console.error(err))
  .finally(async () => await browser.close())
;
ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • I see the logic behind creating an Obj and using the keys, and pass them in for loop. This looks great i am definitely going to try this. But i have two questions. Why is "browser" defined outside the async function but not inside? What is the significance of creating variable const [pages] to const page? I am still learning. Thanks a lot !!! – NewCoder Jun 10 '21 at 02:01
  • 1
    Glad it helps you. Defining `browser` outside of the IIFE enables `.finally(async () => await browser.close())` which is a clean way to close the browser regardless of whether an errors occur or not. `const [page] = await browser.pages()` uses array destructuring to select the first page -- the browser starts out with 1 page so there's no need for `newPage()`. It's the same as `const page = (await browser.pages())[0];`. [This post](https://stackoverflow.com/questions/53939503/puppeteer-doesnt-close-browser/67910262#67910262) explains these details of my Puppeteer setup. – ggorlen Jun 10 '21 at 02:07
  • Makes so much sense ! – NewCoder Jun 10 '21 at 02:17