I'm trying to write an application that will take multiframe screenshots of animated banners, sometimes multiple on a page, by doing the following:
- Create a local folder to store images
- Open a browser instance at the provided url
- After the page is loaded, iterate over all of the iframes on the page that match the provided selector and apply them to the banners variable.
- Apply the x, y, width, height, and id attributes to each banner object
- Iterate over the banners array and for each banner take a screenshot every .5 seconds for 10 seconds (or whatever values I ultimately decide on).
- Close the browser
The issue I have here is I can't seem to figure out where and when to close the browser; all of the examples I look at just seem to have the browser close call at the end of everything, but when I do that I get an error message (shown below) that seems to indicate it's closing the browser before screenshots get going. I'm fairly certain the core of the issue is that I'm still working on having a good understanding of async and predicting when certain things will happen in the code.
Error:
TargetCloseError: Protocol error (Target.activateTarget): Session closed. Most likely the page has been closed.
Full code:
const puppeteer = require('puppeteer');
const os = require('os');
const fs = require('fs');
const path = require('path');
const workDir = './temp';
const REFRESH_SELECTOR = '.icon-button.material-icons[aria-label="refresh"]';
const PREVIEW_SELECTOR = '.dynamic-ad-card-back iframe';
const CHROME_PATHS = {
darwin: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
linux: '/usr/bin/google-chrome',
win32: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
};
const CHROME_PATH = CHROME_PATHS[os.platform()];
const PIXEL_DENSITY = 2;
(async () => {
const browser = await puppeteer.launch({
headless: true,
executablePath: CHROME_PATH,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--single-process',
],
});
if (!fs.existsSync(workDir)) {
fs.mkdirSync(workDir);
console.log(`${workDir} created`)
};
let frameCount = 0;
const page = await browser.newPage();
page.setViewport({width: 1280, height: 6000, deviceScaleFactor: PIXEL_DENSITY});
await page.goto('https://[URL-OBSCURED]', { waitUntil: 'networkidle0' });
console.log('page navigation has happened');
async function getScreenShots() {
console.log('getScreenShots called');
const banners = await page.$$eval(PREVIEW_SELECTOR, iframes => {
return Array.from(iframes, (el) => {
const {x, y, width, height} = el.getBoundingClientRect();
return {
left: x,
top: y,
width,
height,
id: el.id,
};
});
}, PREVIEW_SELECTOR).catch(e => {
console.error(e.message);
});
for (const banner of banners) {
console.log(`current banner ID: ${banner.id}`);
const intervalId = setInterval(async () => {
// await page.click();
await page.screenshot({
clip: {
x: banner.left,
y: banner.top,
width: banner.width,
height: banner.height,
},
path: `${workDir}/screenshot_${banner.id}-frame_index-${frameCount}.png`,
}).then(() => {
console.log(`generating file: screenshot_${banner.id}-frame_index-${frameCount}.png`);
console.log(`current banner: ${banner.id}`);
frameCount++
});
}, 500);
frameCount = 0;
setTimeout(() => {
clearInterval(intervalId);
}, 10000);
}
}
await getScreenShots().catch((e) => console.error(e.message));
await browser.close();
})();
async
/await
that likely lead to yourpage
doing operations simultaneously, leading to a race condition. I would useawait
able timeouts rather than callbacks and get rid of.then
/catch
. Also, make sure toawait
every promise, likepage.setViewport
. This isn't aboutbrowser.close()
. – Monosaccharide