3

So I am trying to use puppeteer to automate some data entry functions in Oracle Cloud applications.

As of now I am able to launch the cloud app login page, enter username and password credentials and click login button. Once login is successful, Oracle opens a homepage for the user. Once this happens if I take screenshot or execute a page.content the screenshot and the content html is from the login page not of the homepage.

How do I always have a reference to the current page that the user is on?

Here is the basic code so far.

const puppeteer = require('puppeteer');
const fs = require('fs');

(async () => {
  const browser = await puppeteer.launch({headless: false});
  let page = await browser.newPage();
  await page.goto('oraclecloudloginurl', {waitUntil: 'networkidle2'});
  await page.type('#userid', 'USERNAME', {delay : 10});
  await page.type('#password', 'PASSWORD', {delay : 10});
  await page.waitForSelector('#btnActive', {enabled : true});
  page.click('#btnActive', {delay : 1000}).then(() => console.log('Login Button Clicked'));

  await page.waitForNavigation();
  await page.screenshot({path: 'home.png'});
  const html = await page.content();
  await fs.writeFileSync('home.html', html);
  await page.waitFor(10000);
  await browser.close();
})();

With this the user logs in fine and the home page is displayed. But I get an error after that when I try to screenshot the homepage and render the html content. It seems to be the page has changed and I am referring to the old page. How can I refer to the context of the current page?

Below is the error:

(node:14393) UnhandledPromiseRejectionWarning: Error: Protocol error (Runtime.callFunctionOn): Cannot find context with specified id undefined
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Aakash
  • 3,101
  • 8
  • 47
  • 78
  • `Cannot find context with specified id undefined` — What produces this error? This part is not in the sampel code, is it? – Vaviloff Jun 04 '18 at 09:26

1 Answers1

0

This code looks problematic for two reasons:

page.click('#btnActive', {delay : 1000}).then(() => console.log('Login Button Clicked'));

await page.waitForNavigation();

The first problem is that the page.click().then() spins off a totally separate promise chain:

page.click() --> .then(...)
     |
     v
page.waitForNavigation()
     |
     v
page.screenshot(...)
     |
     v
    ...

This means the click that triggers the navigation and the navigation are running in parallel and can never be rejoined into the same promise chain. The usual solution here is to tie them into the same promise chain:

// Note: this code is still broken; keep reading!
await page.click('#btnActive', {delay : 1000});
console.log('Login Button Clicked');
await page.waitForNavigation();

This adheres to the principle of not mixing then and await unless you have good reason to.

But the above code is still broken because Puppeteer requires the waitForNavigation() promise to be set before the event that triggers navigation is fired. The fix is:

await Promise.all([
  page.waitForNavigation(),
  page.click('#btnActive', {delay : 1000}),
]);

or

const navPromise = page.waitForNavigation(); // no await
await page.click('#btnActive', {delay : 1000});
await navPromise;

Following this pattern, Puppeteer should no longer be confused about its context.


Minor notes:

  • 'networkidle2' is slow and probably unnecessary, especially for a page you're soon going to be navigating away from. I'd default to 'domcontentloaded'.
  • await page.waitFor(10000); is deprecated along with page.waitForTimeout(), although I realize this is an older post.
ggorlen
  • 44,755
  • 7
  • 76
  • 106