Pyppeteer is displaying a white screen - Does metabase block headless browsing? (ie. Selenium, pyppeteer)

pyppeteer is displaying a white screen when I take a screenshot of the page given a public URL for a dashboard or question.

When I view the html that its viewing, I can see that the only div that is loading is:

<div id="root"> </div>

What this indicates to me is that the javascript is not injecting inside of the root from metabase.

Here is what Chat GPT has given me clues for:

  1. JavaScript Execution: In some cases, Pyppeteer might not execute JavaScript exactly as a regular browser does, especially for complex web apps like Metabase.
  2. Rendering Time: Sometimes, web pages may need more time to load all their elements, especially those depending on JavaScript. You might need to add more wait time to make sure all elements get loaded.
  3. Browser Detection: Some websites might act differently when they detect they're being visited by a headless browser.
  4. Browser Settings: Some specific settings required by the website might not be enabled in the headless browser.

I have tried increasing the wait time to 10 seconds, but still no luck.

Below is the code:

import asyncio
from pyppeteer import launch
from urllib.parse import urlparse
import http.client
import json
import logging

URL = 'https://example.metabase.com/dashboard/4'
USERNAME = 'username'
PASSWORD = 'password'
PATH_TO_SAVE = 'example.png'
WIDTH = 1920
HEIGHT = 1920


def get_session():
    url = urlparse(URL)
    host_name = url.hostname

    if url.scheme == 'https':
        conn = http.client.HTTPSConnection(host_name)
    elif url.scheme == 'http':
        conn = http.client.HTTPConnection(host_name)
    else:
        raise Exception("Please use http or https protocol only")

    payload = json.dumps({"password": PASSWORD, "username": USERNAME})

    headers = {
      'Content-Type': 'application/json'
    }
    conn.request("POST", "/api/session/", payload, headers)
    res = conn.getresponse()
    data = res.read()
    session_resp = json.loads(data.decode("utf-8"))
    return session_resp['id']


async def main():
    session_id =  get_session()
    logging.debug("Loading Browser ...")
    browser = await launch({'defaultViewport': {'width': WIDTH, 'height': HEIGHT}})
    logging.debug("Browser Loaded \n Opening Tab ...")
    page = await browser.newPage()

    await page.setCookie({
        'name': 'metabase.SESSION', 'value': session_id,
        'url': URL
    })
    logging.debug("Go to page ...")
    await page.goto(URL, {'waitUntil': 'networkidle2'})
    logging.debug("Taking Screenshot ...")
    await page.screenshot({'path': PATH_TO_SAVE})
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

you confirm you're using a public dashboard/question? can you post diagnostic info?

@Luiggi this is a public dashboard/question.

What diagnostic info would you need me to provide?

Here is the screenshot that is returned. Its a blank screen.

It seems that the <div id="root"></div> is not being injected with the HTML to form the charts and that the Javascript is not able to render.

I also added:

await page.goto(URL, {'waitUntil': 'networkidle0'})

Here is the HTML content the the pypetteer is viewing:

;"},"translations":{"":{"Metabase":{"msgid":"Metabase","msgstr":["Metabase"]}}}}</script><script>(function() {
  window.MetabaseBootstrap        = JSON.parse(document.getElementById("_metabaseBootstrap").textContent);
  window.MetabaseUserLocalization = JSON.parse(document.getElementById("_metabaseUserLocalization").textContent);
  window.MetabaseSiteLocalization = JSON.parse(document.getElementById("_metabaseSiteLocalization").textContent);

  var configuredRoot = document.head.querySelector("meta[name='base-href']").content;
  var actualRoot = "/";

  // Add trailing slashes
  var backendPathname = document.head.querySelector("meta[name='uri']").content.replace(/\/*$/, "/");
  // e.x. "/questions/"
  var frontendPathname = window.location.pathname.replace(/\/*$/, "/");
  // e.x. "/metabase/questions/"
  if (backendPathname === frontendPathname.slice(-backendPathname.length)) {
    // Remove the backend pathname from the end of the frontend pathname
    actualRoot = frontendPathname.slice(0, -backendPathname.length) + "/";
    // e.x. "/metabase/"
  }

  if (actualRoot !== configuredRoot) {
    console.warn("Warning: the Metabase site URL basename \"" + configuredRoot + "\" does not match the actual basename \"" + actualRoot + "\".");
    console.warn("You probably want to update the Site URL setting to \"" + window.location.origin + actualRoot + "\"");
    document.getElementsByTagName("base")[0].href = actualRoot;
  }

  window.MetabaseRoot = actualRoot;
})();
</script><link href="app/dist/styles.css?10cd540a50370a0ca610" rel="stylesheet"><link href="app/dist/app-public.css?b1d6198e6793a4fcaf98" rel="stylesheet"><script src="app/dist/runtime.bundle.js?b8ee4fd519d373284ec6"></script><script src="app/dist/styles.bundle.js?18863dc168329c0e885e"></script><script src="app/dist/vendor.bundle.js?73f5a201c10433050a68"></script><script src="app/dist/app-public.bundle.js?728b42f44abf5bc2afd1"></script><style>/* dynamic stylesheet */</style></head><body><div id="root"></div></body></html>