Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get readable browser/page errors out of puppeteer-sharp?

I am using puppeteer-sharp to render some pages as PDFs. I'd like to know if the page has any issues rendering at runtime in the browser, so I set up some event handlers:

_page.Error += (sender, args) =>
{
    _logger.LogCritical(args.Error);
};

_page.PageError += (sender, args) =>
{
    _logger.LogError(args.Message);
};

_page.Console += (sender, args) =>
{
    switch (args.Message.Type)
    {
        case ConsoleType.Error:
            _logger.LogError(args.Message.Text);
            break;
        case ConsoleType.Warning:
            _logger.LogWarning(args.Message.Text);
            break;
        default:
            _logger.LogInformation(args.Message.Text);
            break;
    }
};

When I get an error on the page, args.Message.Text seems to just contain "ERROR JSHandle@error". This isn't very helpful.

I tested normal console.log on the page and that logs fine, it seems to be an issue with Errors.

Is there something I need to do to get something readable out of these errors?

Update: I tried accessing args.Message.Args and using JsonValueAsync() on those args, but that seems to cause some async weirdness that breaks the devtools protocol because I started getting timeout errors and then errors complaining about web sockets being broken.

It appears this is an issue with puppeteer itself: https://github.com/GoogleChrome/puppeteer/issues/3397

like image 800
ldam Avatar asked Mar 03 '23 18:03

ldam


1 Answers

So my problem was happening because errors are apparently not serializable in javascript land, so we need to get the browser to run a function to extract the message and any other details we need from the error and return that.

This is what's working for me right now:

_page.Console += async (sender, args) =>
{
    switch (args.Message.Type)
    {
        case ConsoleType.Error:
            try
            {
                var errorArgs = await Task.WhenAll(args.Message.Args.Select(arg => arg.ExecutionContext.EvaluateFunctionAsync("(arg) => arg instanceof Error ? arg.message : arg", arg)));
                _logger.LogError($"{args.Message.Text} args: [{string.Join<object>(", ", errorArgs)}]");
            }
            catch { }
            break;
        case ConsoleType.Warning:
            _logger.LogWarning(args.Message.Text);
            break;
        default:
            _logger.LogInformation(args.Message.Text);
            break;
    }
};

I got the idea from a comment from one of the puppeteer contributors here and ported it to pupeteer-sharp.

The way it's supposed to be done atm is like this:

page.on('console', async msg => {
  // serialize my args the way I want
  const args = await Promise.all(msg.args.map(arg => arg.executionContext().evaluate(arg => {
    // I'm in a page context now. If my arg is an error - get me its message.
    if (arg instanceof Error)
      return arg.message;
    // return arg right away. since we use `executionContext.evaluate`, it'll return JSON value of
    // the argument if possible, or `undefined` if it fails to stringify it.
    return arg;
  }, arg)));
  console.log(...args);
});
like image 186
ldam Avatar answered Apr 26 '23 09:04

ldam