2
votes

I'm trying to create a Puppeteer function in GCP which can be triggered by Pub/Sub messages. The function is callable, but doesn't behave as expected and throws a Timeout Error once browser tries to initialize. Could the trigger possibly be using a NodeJS environment different from HTTP trigger?

I'm also very new to NodeJS, so I apologize ahead of time if the issue is blatantly obvious.

I've created an HTTP trigger for the function which behaves as expected. I copy/paste the Puppeteer Function below into the index.js when creating the Cloud Function, but separated in example for clarity that both triggers are running the identical function.

Puppeteer Function

const puppeteer = require('puppeteer');

scrapeUglyWebsite = () => {
    return new Promise(async(resolve, reject) => {
        await puppeteer.launch({
            headless: true,
            args: ['--no-sandbox']
        })
            .then(async (browser) => {
                const page = await browser.newPage();
                await page.goto('http://suzannecollinsbooks.com/', {waitUntil: 'load', timeout: 0})
                    .then(async () => {
                        //Wait for content to load
                        await page.waitForFunction('document.body !== null && document.body.innerText.includes(\'Jon Scieszka\')');
                        //Evaluate page contents
                        const dom_eval = await page.evaluate(() => document.body.innerText.includes("Here’s a picture of me with a rat"));
                        await browser.close();
                        resolve(dom_eval);
                    });
            }).catch((err) => {
                reject(err);
            });
    });
};

HTTP Trigger - index.js

exports.cloudFunctionTest = (req, res) => {
    scrapeUglyWebsite()
        .then((results) => {
            if(results) {
                res.send('Suzanne Collins takes pictures with rats.');
            } else {
                res.send("Suzzane Collins doesn't take pictures with rats.");
            };
        })
        .catch((err) => {
            res.send(err.toString());
        });

Pub/Sub Trgger - index.js

exports.cloudFunctionTest = (data, context) => {
    scrapeUglyWebsite()
        .then((results) => {
            if(results) {
                console.log('Suzanne Collins takes pictures with rats.');
            } else {
                console.log("Suzzane Collins doesn't take pictures with rats.");
            };
        })
        .catch((err) => {
            console.log(err.toString());
        });
};

package.json

{
  "name": "test",
  "version": "0.0.1",
  "engines": {
    "node": "8"
  },
  "dependencies": {
    "puppeteer": "^1.6.0"
  }
}

HTTP Trigger behaves correctly with the expected result

Suzanne Collins takes pictures with rats.

Pub/Sub Trigger throws the following error with no output

TimeoutError: Timed out after 30000 ms while trying to connect to Chrome! The only Chrome revision guaranteed to work is r662092
1

1 Answers

0
votes

I know this is late but the reason that the TimeoutError occurs is because cloud functions do not automatically wait for async tasks to finish completing. So in exports.cloudFunctionTest, scrapeUglyWebsite() is called but the function does not wait for the promise to be fulfilled, so the program terminates. Hence the error

More info here on how background functions work in NodeJs

In order for the function to wait for scrapeUglyWebsite(), you need to return a promise that completes when scrapeUglyWebsite() and the resulting code is complete.

Personally, I got it to work by simply wrapping the code currently in the function I am exporting in another async function and then returning the promise of the wrapper function.

async function wrapper() {
    try {
        const result = await scrapeUglyWebsite(); 
        if(results) {
            console.log('Suzanne Collins takes pictures with rats.');
        } else {
            console.log("Suzzane Collins doesn't take pictures with rats.");
        };
    } catch (err) {
        console.log(err.toString());
    }
}

Then in the function you want to export:

exports.cloudFunctionTest = (data, context) => {
    return wrapper();
};