What I'm trying to do here is loop through Storybook stories so I can perform visual regression testing on them:
const puppeteer = require('puppeteer');
const { toMatchImageSnapshot } = require('jest-image-snapshot');
expect.extend({ toMatchImageSnapshot });
test('no visual regression for button', async () => {
const selector = 'a[href*="?selectedKind=Buttons&selectedStory="]';
const browser = await puppeteer.launch({headless:false, slowMo: 350});
const page = await browser.newPage();
await page.goto('http://localhost:8080');
const storyLinks = await page.evaluate(() => {
const stories = Array.from(document.querySelectorAll('a[href*="?selectedKind=Buttons&selectedStory="]'));
const links = stories.map(story => story.href);
return links;
});
await storyLinks.forEach( (storyLink) => {
page.goto(storyLink).then(async (res,rej) => {
const screen = await page.screenshot();
return await expect(screen).toMatchImageSnapshot();
});
});
await browser.close();
});
One problem is that I get this because of the await broswer.close() that isn't waiting for everything to finish:
Protocol error (Page.navigate): Target closed.
at Session._onClosed (../../node_modules/puppeteer/lib/Connection.js:209:23)
at Connection._onClose (../../node_modules/puppeteer/lib/Connection.js:116:15)
at Connection.dispose (../../node_modules/puppeteer/lib/Connection.js:121:10)
at Browser.close (../../node_modules/puppeteer/lib/Browser.js:60:22)
at Object.<anonymous>.test (__tests__/visual.spec.js:24:16)
at <anonymous>
This happens for each storyLink except the first.
If I comment out the await browser.close() line, the screenshots are being taken, but not in the expected wait. Instead of each story having one screenshot, the last story is being screenshotted for the amount of stories. For example, I've got 4 stories in total, but I will have 4 screenshots of the last story instead of one for each story.
I don't understand why this behaviour is happening. The storyLinks returned from the page.evaluate funtions are correct, but then everything breaks and I've got no idea why.
Any ideas?
forEach is not good for async-await. Use for..of instead,
for (let storyLink of storyLinks) {
await page.goto(storyLink)
const screen = await page.screenshot();
await expect(screen).toMatchImageSnapshot();
};
Related
So, I have a front end in React.js or Ember.js - not sure. I'm just trying to automate some testing for it. When looking at the HTML in the Chrome Dev Tools, I see
<label class="MuiFormLabel-root-16 MuiInputLabel-root-5 MuiInputLabel-formControl-10 MuiInputLabel-animated-13 MuiInputLabel-outlined-15" data-shrink="false">Username</label>
This is set in an iframe (which isn't too important for this issue). I'm trying to get the ElementHandler using the puppeteer function
frame.$(selector)
How do I get the selector given
Username
I've tried a few things, but with little success.
If I understand correctly, you need to find an element by its text content. If so, these are at least two ways:
const puppeteer = require('puppeteer');
(async function main() {
try {
const browser = await puppeteer.launch(); // { headless: false, defaultViewport: null, args: ['--lang=en'] }
const [page] = await browser.pages();
await page.goto('https://example.org/');
const element1 = await page.evaluateHandle(() => {
return [...document.querySelectorAll('h1')].find(h1 => h1.innerText === 'Example Domain');
});
console.log(await (await element1.getProperty('outerHTML')).jsonValue());
const [element2] = await page.$x('//h1[text()="Example Domain"]');
console.log(await (await element2.getProperty('outerHTML')).jsonValue());
await browser.close();
} catch (err) {
console.error(err);
}
})();
My solution was to place HTML data attributes in the front end code so that I can easily select them.
I've seen this asked around but I can't pinpoint what I'm doing wrong.
Error: UnhandledPromiseRejectionWarning: Error: Evaluation failed: ReferenceError: page is not defined
const page = await browser.newPage();
await page.goto('https://example.com');
const getAllElements = await page.$$eval('.aclass', links => {
links.map(link => {
page.hover(link);
page.screenshot({path: `example${link}.png`});
})
})
Expected behavior is that I go to example.com, I then get all the .aclass elements. Return those as 'links" then I map over each link, which should give me each element in link. I then am expecting to be able to page.hover and page.screenshot. However this is where I get the error that page is not defined. Any idea what I'm doing wrong?
hover and screenshot returns Promise.
Don't pass page to page.$$eval. Instead you can do the following:
const page = await browser.newPage();
await page.goto('https://example.com');
const getAllElements = await page.$$('.aclass');
for (let [i, link] of getAllElements.entries()) {
await link.hover();
await link.screenshot({path: `example${i}.png`});
}
I am currently doing a project that needs to scrape a data from the search result in carousell.ph
I basically made a sample HTML and replicate the output HTML of carousell, so far the javascript work except when I tried to migrate it using puppeteer it always gives me an error.
The task is basically get all the product list from the search url "https://www.carousell.ph/search/iphone"
Here's the code I made.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
let url = 'https://www.carousell.ph/search/iphone';
await page.goto(url, {waitUntil: 'load', timeout: 10000});
await page.setViewport({ width: 2195, height: 1093 });
await page.screenshot({ fullPage: true, path: 'carousell.png' });
document.querySelectorAll('main').forEach(main => {
main.querySelectorAll('a').forEach(product => {
const product_details = product.querySelectorAll('p');
const productName = product.textContent;
const productHref = product.getAttribute('href');
console.log(product_details[0].textContent + " - "+ product_details[1].textContent);
});
});
await browser.close()
})()
As #hardkoded stated, document is not something that is out of the box in puppeteer, it's dogma in the browser, but not in Node.js. You also do not need to for each in Node.js. The Map Technique outlined in this video is very helpful and quick. I'd make sure also to keep await on your loop or map technique, because the function is asynchronous so you want to make sure the promise comes back resolved.
Map technique
An extremely fast way to get many elements into an array from a page is to use a function like below. So instead of getting an array of the elements and then looping them for their properties. You can create a function like this below using $$eval and map. The result is a formatted JSON array that takes all the looping out of the equation.
const links = await first_state_list.$$eval("li.stateList__item", links =>
links.map(ele2 => ({
State_nme: ele2.querySelector("a").innerText.trim(), //GET INNER TEXT
State_url: ele2.querySelector("a").getAttribute("href") //get the HREF
}))
);
Already made it work.
const puppeteer = require('puppeteer');
async function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage();
let searchItem = 'k20&20pro';
let carousellURL = 'https://www.carousell.ph/search/' + searchItem;
await page.goto(carousellURL, {waitUntil: 'load', timeout: 100000});
//await page.goto(carousellURL, {waitUntil: 'networkidle0'});
await page.setViewport({
width: 2195,
height: 1093
});
await page.evaluate(() => {
window.scrollBy(0, window.innerHeight);
})
await timeout(15000);
await page.screenshot({
fullPage: true,
path: 'carousell.png'
});
var data = await page.evaluate(() =>
Array.from(
document.querySelectorAll('main div a:nth-child(2)')).map(products => products.href
)
)
var i;
for (i = 0; i < data.length; i++) {
console.log(data[i]);
// comment this section but this will open the page to get product details.
//await page.goto(data[1], {"waitUntil" : "networkidle0"});
// inner product page details
// this will get the title
// document.querySelectorAll('h1')[0].innerText;
// this will get the amount
// document.querySelectorAll('h2')[0].innerText;
// this will get the description
// document.querySelectorAll('section div div:nth-child(4) p')[0].innerText;
// this will get sellers name
// document.querySelectorAll('div div:nth-child(2) a p')[0].innerText;
let ss_filename = 'carousellph_'+searchItem+'_'+i+'.png';
console.log(ss_filename);
console.log("\r\n");
//await page.screenshot({ fullPage: false, path: ss_filename });
}
await browser.close()
})()
I have a webpage and automated through puppeteer script as follow, I want to measure the response time and other metrics like what is the time taken to load the dom content, time for the first byte, etc.
const puppeteer = require('puppeteer');
const myURL = "http://localhost:8080/#/login";
const Username = "jmallick";
const BlockName = "TEST99";
const Password = "Test1234";
async function loginHandler (){
return new Promise (async (resolve, reject) =>{
let browserLaunch = await puppeteer.launch({headless : false,
args: ['--window-size=1920,1040']});
try{
const pageRender = await browserLaunch.newPage();
await pageRender.goto(myURL, {waitUntil: 'networkidle0'});
pageRender.setViewport() --> didn't worked
<want to measure the details here>
await pageRender.type('#txtUserName',Username);
await pageRender.type('#txtPassword',Password);
await pageRender.type('#txtBlockName',FirmName);
await Promise.all([
pageRender.click('#fw-login-btn'),
pageRender.waitForNavigation({ waitUntil: 'networkidle0' }),
pageRender.setViewport() --> didn't worked
<want to measure the details here>
]);
const homePage = await pageRender.$('[href="#/home"]');
await homePage.click({waitUntil: 'networkidle0'});
<want to measure the details here>
}
catch (e){
return reject(e);
}
finally{
browserLaunch.close();
}
});
}
loginHandler().then(console.log).catch(console.error);
Tried with pageRender.setViewport() but didn't work. Checked other pages but couldn't find the same match.
I want to close pages when puppeteer faces on any error , sometimes page the page that i try to load crashes and it doesnt call .close();
(async () => {
const page = await browser.newPage();
await page.setViewport({width: resWidth, height: resHeight});
await page.goto(d["entities"]["urls"][0]["expanded_url"], {timeout :90000});
await page.screenshot({path: './resimdata/'+d['id']+'.png' ,fullPage: true});
await page.close();
})();
There is an issue/PR on puppeteer repo regarding this which will be helpful in similar situation.
Related Issue link: https://github.com/GoogleChrome/puppeteer/issues/952
Meanwhile, you can try this little hack, if the PR is there on version 0.12+, we don't have to worry about the following code.
(async() => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
function handleClose(msg){
console.log(msg);
page.close();
browser.close();
process.exit(1);
}
process.on("uncaughtException", () => {
handleClose(`I crashed`);
});
process.on("unhandledRejection", () => {
handleClose(`I was rejected`);
});
await page.goto("chrome://crash");
})();
Which will output something like the following,
▶ node app/app.js
I was rejected