skip to Main Content

I’m new to JavaScript and to the Puppeteer Library. I know what async programming is and I’ve been playing around with it in JavaScript but when I try to use it with Puppeteer I get errors.

This is my index.js file:

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
  });

  // Disable Facebook notifications
  const context = await browser.defaultBrowserContext();
  await context.overridePermissions('https://www.facebook.com', ['geolocation', 'notifications']);

  const page = await browser.newPage();
  const logger = await myLog.logger;

  console.log('About to use function.');
  await utils.webAction(
    logger, 'Enter Facebook',
    (() => (page.goto(constants.linkHomepage, {waitUntil: ['domcontentloaded', 'networkidle2']})))
  );
  console.log('Content loaded');

  // User Login
  await utils.webAction(
    logger, 'Wait for loginEmail element',
    (() => (page.waitForSelector(constants.cssLoginEmail, {visible: true}))),
  );
  // Using a fake (incorrect) xpath to trigger the "catch" block
  await utils.webAction(
    logger, 'Enter user email using incorrect xpath', utils.webType,
    page, "Fake user", "fake xpath/css"  // Arguments of `webType`
  );
})();

And the utils.js file:

module.exports = {
  webAction: function (logger, msg, func, ...args)
  {
    return new Promise((resolve) => {
      let actionMessage, actionLevel;
      try
      {
        func(...args);
        actionMessage = msg;
        actionLevel = 'info';
      }
      catch (error)
      {
        actionMessage = "Error while executing " + msg + " function.n---n" + error.message + "n---";
        actionLevel = 'error';
      }
      finally
      {
        logger.log({
          level: actionLevel,
          message: actionMessage
        })
      }
      console.log('Inside resolve Promise');
      resolve('Fullfilment value of Promise');
      console.log('Last line if resolve body');
    })
  },

  // Type into input field
  webType: function (page, text, xpath)
  {
    page.type(
      xpath, text,
      {delay: getRandomIntInRange(constants.minSpeed, constants.maxSpeed)}
    );
  },

};

I created the webAction function because I want the code to look cleaner and also to keep track of where the program crashes (I’m open to other ways to achieve this).

This is the error I’m getting:

About to use function.
info: Enter Facebook {"timestamp":"2024-01-18 16:43:59"}
Inside resolve Promise
Last line if resolve body
Content loaded
info: Wait for loginEmail element {"timestamp":"2024-01-18 16:43:59"}
Inside resolve Promise
Last line if resolve body
info: Enter user email using incorrect xpath {"timestamp":"2024-01-18 16:43:59"}
Inside resolve Promise
Last line if resolve body
/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/common/CallbackRegistry.js:85
            this._reject(callback, new Errors_js_1.TargetCloseError('Target closed'));
TargetCloseError: Protocol error (Runtime.callFunctionOn): Target closed
    at CallbackRegistry.clear (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/common/CallbackRegistry.js:85:36)
    at CdpCDPSession._onClosed (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/CDPSession.js:113:25)
    at Connection.onMessage (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/Connection.js:132:25)
    at WebSocket.<anonymous> (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/node/NodeWebSocketTransport.js:52:32)
    at callListener (/home/user/Projects/Javascript/Facebook/node_modules/ws/lib/event-target.js:290:14)
    at WebSocket.onMessage (/home/user/Projects/Javascript/Facebook/node_modules/ws/lib/event-target.js:209:9)
    at WebSocket.emit (node:events:514:28)
    at Receiver.receiverOnMessage (/home/user/Projects/Javascript/Facebook/node_modules/ws/lib/websocket.js:1192:20)
    at Receiver.emit (node:events:514:28)
    at Receiver.dataMessage (/home/user/Projects/Javascript/Facebook/node_modules/ws/lib/receiver.js:560:14) {
  cause: ProtocolError
      at <instance_members_initializer> (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/common/CallbackRegistry.js:96:14)
      at new Callback (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/common/CallbackRegistry.js:100:16)
      at CallbackRegistry.create (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/common/CallbackRegistry.js:32:26)
      at Connection._rawSend (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/Connection.js:91:26)
      at CdpCDPSession.send (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/CDPSession.js:78:33)
      at next (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-extra-plugin-stealth/evasions/sourceurl/index.js:34:41)
      at CdpCDPSession.send (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-extra-plugin-stealth/evasions/sourceurl/index.js:75:16)
      at #evaluate (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js:211:50)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async ExecutionContext.evaluateHandle (/home/user/Projects/Javascript/Facebook/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js:178:16)
}

Node.js v18.17.0

I want to be able to use the webAction function so if you have any idea of how I can fix the error you are more than welcome.

In the output it says that it entered Facebook correctly but this is not true, as as soon as the browser opened it closed immediately!
And obviously it doesn’t complete correctly the order statements (waitForSelector & utils.webType). Can someone explain me what is happening and how to be able to run this code without any error?

2

Answers


  1. In your webAction function, you are calling these functions but you are not awaiting the resolution of these promises.

    You need to change your utils.js file to await the result of the func call:

    module.exports = {
      webAction: async function (logger, msg, func, ...args)
      {
        return new Promise(async (resolve) => {
          let actionMessage, actionLevel;
          try
          {
            await func(...args);
            actionMessage = msg;
            actionLevel = 'info';
          }
          catch (error)
          {
            actionMessage = "Error while executing " + msg + " function.n---n" + error.message + "n---";
            actionLevel = 'error';
          }
          finally
          {
            logger.log({
              level: actionLevel,
              message: actionMessage
            })
          }
          console.log('Inside resolve Promise');
          resolve('Fullfilment value of Promise');
          console.log('Last line if resolve body');
        })
      },
    
      webType: async function (page, text, xpath)
      {
        await page.type(
          xpath, text,
          {delay: getRandomIntInRange(constants.minSpeed, constants.maxSpeed)}
        );
      },
    };
    
    Login or Signup to reply.
  2. The rule with promises is that if you care about ordering, or the result (which you do, 99% of the time), you need to await the promise. To await a promise from a function, the promise chain needs to be returned to the caller so it can be awaited there. Currently, your webAction function doesn’t await func(). webType doesn’t await page.type().

    In effect, your script fires off dozens of concurrent promises without awaiting any results, so ordering is arbitrary and errors are virtually guaranteed–the browser closes along with all of the other operations.

    While this provides the fix to your code, the design could be improved. One approach to logging your Puppeteer operations is to use a Proxy and forward every call to page:

    const puppeteer = require("puppeteer"); // ^21.6.0
    
    const makeLogger = page => {
      return msg => {
        const handler = {
          get(target, propKey, receiver) {
            return (...args) => {
              // optional pre-page operation logging here
              return page[propKey](...args)
                .then(result => {
                  // happy path logging here
                  console.log(`${propKey}: ${msg}`);
                  return result;
                })
                .catch(err => {
                  // sad path logging here
                  console.error(`${msg} (${err.message})`);
                });
            };
          }
        };
        return new Proxy({}, handler);
      };
    };
    
    // sample usage:
    let browser;
    (async () => {
      browser = await puppeteer.launch({headless: "new"});
      const [page] = await browser.pages();
      const p = makeLogger(page);
      await p("example.com").goto("https://www.example.com");
      const text = await p("get text content").$eval("h1", el => el.textContent);
      console.log("  got text:", text);
      await p("test throwing").evaluate("notExists.foobar");
      console.log("  done");
    })()
      .catch(err => console.error(err))
      .finally(() => browser?.close());
    

    Output:

    goto: example.com
    $eval: get text content
      got text: Example Domain
    test throwing (notExists is not defined)
      done
    

    You should be able to integrate your custom logger here and adjust to taste. As with page, use a closure for logger so you don’t have to repeatedly pass the same parameters on each call.

    Now, if messing with the Puppeteer code isn’t to your liking, you could keep state in a closure and set the log message on a separate line:

    const puppeteer = require("puppeteer"); // ^21.6.0
    
    const makeLogger = page => {
      let message;
      const handler = {
        get(target, propKey, receiver) {
          return (...args) => {
            // optional pre-page logging here
            return page[propKey](...args)
              .then(result => {
                // happy path logging here
                console.log(`${propKey}: ${message}`);
                return result;
              })
              .catch(err => {
                // sad path logging here
                console.error(`${message} (${err.message})`);
              });
          };
        }
      };
      const log = msg => {
        message = msg;
      };
      return {page: new Proxy({}, handler), log};
    };
    
    // sample usage:
    let browser;
    (async () => {
      browser = await puppeteer.launch({headless: "new"});
      const [pg] = await browser.pages();
      const {page, log}  = makeLogger(pg);
    
      log("example.com");
      await page.goto("https://www.example.com");
      log("get text content");
      const text = await page.$eval("h1", el => el.textContent);
      console.log("  got text:", text);
      log("test throwing");
      await page.evaluate("notExists.foobar");
      console.log("  done");
    })()
      .catch(err => console.error(err))
      .finally(() => browser?.close());
    

    The issue here is that once you bring in concurrency and Promise.all, it would be difficult to keep operations separate. But at least you don’t need to modify your Puppeteer code to add the log calls.

    All that said, for most scripts it’s sufficient to rely on Puppeteer’s stack traces and log them in per-function catch blocks. This has the stack trace and should give plenty of debugging info. But the answer here should still provide some food for thought.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search