How to handle popover windows with Puppeteer - javascript

I'm writing a script to purchase items on Amazon.
const puppeteer = require('puppeteer');
// Insert personal credentials
const email = '';
const password = '';
function press_enter(page) {
return Promise.all([
page.waitForNavigation({waitUntil:'networkidle2'}),
page.keyboard.press(String.fromCharCode(13))
]);
}
function click_wait(page, selector) {
return Promise.all([
page.waitForNavigation({waitUntil:'networkidle2'}),
page.click(selector)
]);
}
(async () => {
const browser = await puppeteer.launch({headless:false, defaultViewport:null, args: ['--start-maximized']});
const page = (await browser.pages())[0];
await page.goto('https://www.amazon.it/');
await click_wait(page, "a[data-nav-role='signin']");
await page.keyboard.type(email);
await press_enter(page);
await page.keyboard.type(password);
await press_enter(page);
// Search for the "signout" button as login proof
if(await page.$('#nav-item-signout') !== null) console.log('Login done!');
else return console.log('Something went wrong during login');
// Navigate to the product page
await page.goto('https://www.amazon.it/dp/B07RL2VWXQ');
// Click "buy now" (choose either Option A or Option B)
// Option A: Here the code get stuck since the page isn't refreshing and page.waitForNavigation() will reach its timeout
// await click_wait(page, "#buy-now-button");
// Option B: Waiting time manually set to 5 seconds (it should be more than enough for popover to fully load)
await Promise.all([page.waitForTimeout(5000), page.click('#buy-now-button')]);
// Conclude the purchase
await click_wait(page, '#turbo-checkout-pyo-button');
})();
So far I can login to Amazon, navigate to a product page and click the Buy Now button.
Then, if delivery address and payment option are all set up, (depending on Amazon domain) it may show up a pop-over box to conclude the purchase.
I wasn't able to replicate the popover response on .com and .co.uk, it seems that these domains will redirect you on a totally new page.
When I explore the page with Chrome Developer Tools I actually see the new chunk of the page being loaded (<!DOCTYPE html>) but I don't know where the representation of this element resides in Puppeteer.
If I use click_wait() to click Buy Now, the script gets stuck (it only returns after the default timeout of page.waitForNavigation()) so it's not considered a refreshing of the page. But even if I just wait a few seconds after clicking Buy Now and then attempt to click input[id='turbo-checkout-pyo-button'] (the orange button "Ordina") Puppeteer throws an error cause it can't find the element, despite it being clearly loaded.

Related

How can I check if there are two web tabs open in Playwright?

I was wondering if there is a way to verify using playwright that there are 2 web tabs open...
In the code below I click on a button and verify that google has opened.
BUT!
I also would like to verify that there are now 2 tabs open and wanted to know if it can be done with some thing like .toHaveCount(2)
OR
would you guys recommend something better?
test('Click button link test using blank target', async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(<my weblink>);
const newPagePromise = new Promise((resolve) => context.once('page', resolve));
// Button locator used to click the button to open the link
const buttonLocator = page.locator('.MuiButton-root');
// Click the button
await buttonLocator.click();
// Verify that after clicking on the page that the correct URL is present
const newPage = await newPagePromise;
expect(newPage.url()).toContain('https://www.google.ca');
});
You can use browserContext.pages() to get all the pages opened in the context. You can then use length and assert the values like this
const totPages = context.pages().length
expect(totPages).toHaveCount(2);

Close the page after certain interval [Puppeteer]

I have used puppeteer for one of my projects to open webpages in headless chrome, do some actions and then close the page. These actions, however, are user dependent. I want to attach a lifetime to the page, where it closes automatically after, say 30 minutes, of opening irrespective of whether any action is performed or not.
I have tried setTimeout() functionality of Node JS but it didn't work (or I just couldn't figure how to make it work).
I have tried the following:
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.connect({browserURL: browser_url});
const page = await browser.newPage();
// timer starts ticking here upon creation of new page (maybe in a subroutine and not block the main thread)
/**
..
Do something
..
*/
// timer ends and closePage() is triggered.
const closePage = (page) => {
if (!page.isClosed()) {
page.close();
}
}
But this gives me the following error:
Error: Protocol error: Connection closed. Most likely the page has been closed.
Your provided code should work as excepted. Are you sure the page is still opened after the timeout and it is indeed the same page?
You can try this wrapper for opening pages and closing them correctly.
// since it is async it won't block the eventloop.
// using `await` will allow other functions to execute.
async function openNewPage(browser, timeoutMs) {
const page = await browser.newPage()
setTimeout(async () => {
// you want to use try/catch for omitting unhandled promise rejections.
try {
if(!page.isClosed()) {
await page.close()
}
} catch(err) {
console.error('unexpected error occured when closing page.', err)
}
}, timeoutMs)
}
// use it like so.
const browser = await puppeteer.connect({browserURL: browser_url});
const min30Ms = 30 * 60 * 1000
const page = await openNewPage(browser, min30Ms);
// ...
The above only closes the Tabs in your browser. For closing the puppeteer instance you would have to call browser.close() which could may be what you want?
page.close returns a promise so you need to define closePage as an async function and use await page.close(). I believe #silvan's answer should address the issue, just make sure to replace if condition
if(page.isClosed())
with
if(!page.isClosed())

Puppeteer doesn't find button, doesn't recognize loaded website

First time using Puppeteer and trying to simply click this button
after clicking the deny cookies button. That's my code:
await page.goto('https://myurl.com');
await page.click('a.cc-btn.cc-deny');
// await page.waitForNavigation();
await page.waitForSelector("#detailview_btn_order", {visible: true});
await page.click("#detailview_btn_order");
Clicking the deny cookies button works like a charm. However, it seems the second button can't be identified by Puppeteer. If I don't use waitForSelector it just says it can't find it. If I use it, I get a timeout after 30 seconds even though the website finishes loading after 5 seconds. If I uncomment waitForNavigation (regardless of what options I use) I get a timeout there, even thoug the site loads within seconds. What am I doing wrong? Thanks!
Can you try this:
await page.goto('https://myurl.com');
await Promise.all([
page.click('a.cc-btn.cc-deny'),
page.waitForNavigation(),
]);
const iframeElement = await page.waitForSelector("#my-iframe");
const frame = await iframeElement.contentFrame();
await frame.waitForSelector("#detailview_btn_order", {visible: true});
await frame.click("#detailview_btn_order");
Sometimes there is a race condition between a click and navigation.

Puppeteer how to check if page is navigated and perform task if not navigated

On the login page, I'm trying to figure out whether the google recaptcha appears or not. If it does, I want to run a block of code and otherwise navigate as usual.
await page.goto(
url
);
await page.waitForSelector("#username");
await page.type("#username", process.env.EMAIL);
await page.type("#password", process.env.PSWD);
await page.$eval("#signIn > div > button", (el) => el.click()) //this line sometimes triggers recaptcha
{//here wait for navigation and check if google captcha appears}
//then run the following code:
await page.solveRecaptchas();
await Promise.all([
page.waitForNavigation(),
page.click("#signIn"),
]);
I've tried using page.waitForNavigation but it causes timeout if recaptcha appears. What can I do to run the bottom block of code ONLY if google recaptcha appears?
I also tried conditionally running the block of code on if recaptcha-token is present but I checked the dom and recaptcha element is always present and only prompts image select randomly. Basically I'm available to navigate sometimes without having to perform any captcha and sometimes i'm prompted with image select.
Thanks!
Maybe something like this?
const [_, navigation] = await Promise.allSettled([
element.click(),
page.waitForNavigation(),
]);
if (navigation.status === 'fulfilled') /* There was navigation. */;
else /* There was timeout, no navigation. */;

How to turn headless on after launch? [duplicate]

This question already has answers here:
Can the browser turned headless mid-execution when it was started normally, or vice-versa?
(2 answers)
Closed 5 months ago.
I'd like to load the page with headless off to let me login.
After login I want to hide it, turning on the headless and let it do what it has to do.
How can I turn on/off the headless after launch?
You cannot toggle headless on fly. But you can share the login using cookies and setCookie if you want.
We will create a simple class to keep the code clean (or that's what I believe for these type of work since they usually get big later). You can do this without all these complexity though. Also, Make sure the cookies are serialized. Do not pass array to toe setCookie function.
There will be three main functions.
1. init()
To create a page object. Mostly to make sure the headless and headful version has similar style of browsing, same user agent etc. Note, I did not include the code to set user agents, it's just there to show the concept.
async init(headless) {
const browser = await puppeteer.launch({
headless
});
const page = await browser.newPage();
// do more page stuff before loading, ie: user agent and so on
return {
page,
browser
};
}
2. getLoginCookies()
Example of showing how you can get cookies from the browser.
// will take care of our login using headful
async getLoginCookies() {
const {
page,
browser
} = await this.init(false)
// asume we load page and login here using some method
// and the website sets some cookie
await page.goto('http://httpbin.org/cookies/set/authenticated/true')
// store the cookie somewhere
this.cookies = await page.cookies() // the cookies are collected as array
// close the page and browser, we are done with this
await page.close();
await browser.close();
return true;
}
You won't need such function if you can provide cookies manually. You can use EditThisCookie or any cookie editing tool. You will get an array of all cookies for that site. Here is how you can do this,
3. useHeadless()
Example of showing how you can set cookies to a browser.
// continue with our normal headless stuff
async useHeadless() {
const {
page,
browser
} = await this.init(true)
// we set all cookies we got previously
await page.setCookie(...this.cookies) // three dots represents spread syntax. The cookies are contained in a array.
// verify the cookies are working properly
await page.goto('http://httpbin.org/cookies');
const content = await page.$eval('body', e => e.innerText)
console.log(content)
// do other stuff
// close the page and browser, we are done with this
// deduplicate this however you like
await page.close();
await browser.close();
return true;
}
4. Creating our own awesome puppeteer instance
// let's use this
(async () => {
const loginTester = new myAwesomePuppeteer()
await loginTester.getLoginCookies()
await loginTester.useHeadless()
})()
Full Code
Walk through the code to understand it better. It's all commented.
const puppeteer = require('puppeteer');
class myAwesomePuppeteer {
constructor() {
// keeps the cookies on the class scope
this.cookies;
}
// creates a browser instance and applies all kind of setup
async init(headless) {
const browser = await puppeteer.launch({
headless
});
const page = await browser.newPage();
// do more page stuff before loading, ie: user agent and so on
return {
page,
browser
};
}
// will take care of our login using headful
async getLoginCookies() {
const {
page,
browser
} = await this.init(false)
// asume we load page and login here using some method
// and the website sets some cookie
await page.goto('http://httpbin.org/cookies/set/authenticated/true')
// store the cookie somewhere
this.cookies = await page.cookies()
// close the page and browser, we are done with this
await page.close();
await browser.close();
return true;
}
// continue with our normal headless stuff
async useHeadless() {
const {
page,
browser
} = await this.init(true)
// we set all cookies we got previously
await page.setCookie(...this.cookies)
// verify the cookies are working properly
await page.goto('http://httpbin.org/cookies');
const content = await page.$eval('body', e => e.innerText)
console.log(content)
// do other stuff
// close the page and browser, we are done with this
// deduplicate this however you like
await page.close();
await browser.close();
return true;
}
}
// let's use this
(async () => {
const loginTester = new myAwesomePuppeteer()
await loginTester.getLoginCookies()
await loginTester.useHeadless()
})()
Here is the result,
➜ node app.js
{
"cookies": {
"authenticated": "true"
}
}
So in short,
You can use the cookies function to get cookies.
You can use extensions like Edit This Cookie to get cookies from your normal browser.
You can use setCookie to set any kind of cookie you get from browser.

Categories

Resources