I’ve made a serverless functions at Netlify. It’s a simple browser automation function with Puppeteer:
const chromium = require("@sparticuz/chromium");
const puppeteer = require('puppeteer-core');
chromium.setHeadlessMode = true;
chromium.setGraphicsMode = false;
exports.handler = async function (event, context) {
const browser = await puppeteer.launch({
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: process.env.CHROME_EXECUTABLE_PATH || await
chromium.executablePath,
headless: chromium.headless,
});
await browser.close();
return {
statusCode: 200,
body: JSON.stringify({
status: "The 'Element' function works!"
})
};
}
The function fairly works at the local env. Yet, when deployed to the Netlify it returns the following error:
This function has crashed
An unhandled error in the function code triggered the following message:
TypeError - The “file” argument must be of type string. Received function executablePath
Stack trace
TypeError [ERR_INVALID_ARG_TYPE]: The "file" argument must be of type string. Received function executablePath
at new NodeError (node:internal/errors:387:5)
at validateString (node:internal/validators:162:11)
at normalizeSpawnArguments (node:child_process:528:3)
at Object.spawn (node:child_process:726:13)
at new Process (/var/task/node_modules/@puppeteer/browsers/lib/cjs/launch.js:143:87)
at launch (/var/task/node_modules/@puppeteer/browsers/lib/cjs/launch.js:80:12)
at ChromeLauncher.launch (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ProductLauncher.js:90:54)
at async Runtime.exports.handler (/var/task/functions/element.js:11:21)
Seems to me a cloud server fails to get chromium.executablePath for the browser launch.
Update
after I output the const executablePath = await chromium.executablePath;
I got the following:
async executablePath(input) {
/**
* If the `chromium` binary already exists in /tmp/chromium, return it.
*/
if ((0, node_fs_1.existsSync)("/tmp/chromium") === true) {
return Promise.resolve("/tmp/chromium");
}
/**
* If input is a valid URL, download and extract the file. It will extract to /tmp/chromium-pack
* and executablePath will be recursively called on that location, which will then extract
* the brotli files to the correct locations
*/
if (input && (0, helper_1.isValidUrl)(input)) {
return this.executablePath(await(0, helper_1.downloadAndExtract)(input));
}
/**
* If input is defined, use that as the location of the brotli files,
* otherwise, the default location is ../bin.
* A custom location is needed for workflows that using custom packaging.
*/
input ?? (input = (0, node_path_1.join)(__dirname, "..", "bin"));
/**
* If the input directory doesn't exist, throw an error.
*/
if (!(0, node_fs_1.existsSync)(input)) {
throw new Error(`The input directory "${input}" does not exist.`);
}
// Extract the required files
const promises = [lambdafs_1.default.inflate(`${input}/chromium.br`)];
if (this.graphics) {
// Only inflate graphics stack if needed promises.push(lambdafs_1.default.inflate(`${input}/swiftshader.tar.br`));
}
if ((0, helper_1.isRunningInAwsLambda)()) {
// If running in AWS Lambda, extract more required files
promises.push(lambdafs_1.default.inflate(`${input}/aws.tar.br`));
}
// Await all extractions
const result = await Promise.all(promises);
// Returns the first result of the promise, which is the location of the `chromium` binary
return result.shift();
}
Does it mean that await chromium.executablePath
is not resolved regardless of await
?