4

Problem: Using puppeteer to get the screengrab of a website. Works fine on dev machine but throws below exception when deployed to Azure Functions on the cloud.

Environment: on Azure(Node 12, Linux, Consumption plan), Function triggered using service bus topic.

Error:

Result: Failure Exception: Error: Failed to launch the browser process! spawn 
/home/site/wwwroot/node_modules/puppeteer/.local-chromium/linux-818858/chrome-linux/chrome 
EACCES TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md 
Stack: Error: Failed to launch the browser process! 
spawn /home/site/wwwroot/node_modules/puppeteer/.local-chromium/linux-818858/chrome-linux/chrome 
EACCES TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md 
at onClose (/home/site/wwwroot/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:193:20) 
at ChildProcess.<anonymous> (/home/site/wwwroot/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:185:85)
at ChildProcess.emit (events.js:314:20) at Process.ChildProcess._handle.onexit (internal/child_process.js:274:12)
at onErrorNT (internal/child_process.js:470:16) at processTicksAndRejections (internal/process/task_queues.js:84:21)

I followed the recommendations that are on puppeteer troubleshoot document but still having the same issue.

Things I tried for lunch setting

let browser = await puppeteer.launch({ ignoreDefaultArgs: ['--disable-extensions'] });

let browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });

let browser = await puppeteer.launch({ headless: true });   

let browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });

None of the above worked. They all throw the same error.

I checked FTPing into the function and the chrome file puppeteer is looking for, exists.

Thanks in advance.

Venkata K. C. Tata
  • 5,539
  • 4
  • 22
  • 35

3 Answers3

7

Azure has the necessary dependencies for running headless Chromium in the Linux Consumption plan. So we can use the package puppeteer in Azure function. But we need to deploy the app using remote build. For more details, please refer to the Azure feedback and the blog.

For example

  1. Create Azure function app enter image description here

  2. Create Azure function project

a. Install package

  npm install puppeteer

b. function.json

{
  "bindings": [
    {
      "name": "mySbMsg",
      "type": "serviceBusTrigger",
      "direction": "in",
      "topicName": "bowman1012",
      "subscriptionName": "test",
      "connection": "bowman1012_SERVICEBUS"
    },
    {
      "type": "blob",
      "direction": "out",
      "name": "outputBlob",
      "path": "outcontainer/{rand-guid}.png",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

c. code

const puppeteer = require("puppeteer");

module.exports = async function (context, mySbMsg) {
  context.log(
    "JavaScript ServiceBus topic trigger function processed message",
    mySbMsg
  );
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto("https://google.com/");
  const screenshotBuffer = await page.screenshot({ fullPage: true });
  await browser.close();
  context.bindings.outputBlob = screenshotBuffer;
};

  1. Add .funcignore file in the root project folder
*.js.map
*.ts
.git*
.vscode
local.settings.json
test
tsconfig.json
node_modules
  1. Deploy to Azure
func azure functionapp publish $appName --build remote
  1. Test enter image description here enter image description here

Update

Since you use typescript to create Azure function, we need to update .funcignore as the following

*.js.map
.git*
.vscode
local.settings.json
test
node_modules

For example

My function code index.ts

import { AzureFunction, Context } from "@azure/functions";
import { ServiceBusMessage } from "@azure/service-bus";
import puppeteer from "puppeteer";
import { BlobServiceClient } from "@azure/storage-blob";

const serviceBusTopicTrigger: AzureFunction = async function (
  context: Context,
  mySbMsg: ServiceBusMessage
): Promise<void> {
  try {
    const promotionId = context.bindingData.userProperties.promotionId;
    context.log(
      "Player Screen Grabber ServiceBus topic trigger function processing message started",
      promotionId
    );
    const playerURL = "https://www.google.com/";
    let browser = await puppeteer.launch({ headless: true });
    let page = await browser.newPage();
    await page.goto(playerURL, { waitUntil: "networkidle2" });
    await page.setViewport({ width: 1920, height: 1080 });
    const screenshotBuffer = await page.screenshot({
      encoding: "binary",
    });
    await page.close();
    await browser.close();
    // the storage account connection string
    const constr = process.env["AzureWebJobsStorage"] + "";
    const blobserviceClient = BlobServiceClient.fromConnectionString(constr);
    const containerClient = blobserviceClient.getContainerClient(
      "outcontainer"
    );
    const blob = containerClient.getBlockBlobClient(`${promotionId}.png`);
    await blob.uploadData(screenshotBuffer);
    context.log(
      "Player Screen Grabber ServiceBus topic trigger function processing message ended",
      promotionId
    );
  } catch (error) {
    throw error;
  }
};

export default serviceBusTopicTrigger;

Deploy to Azure

func azure functionapp publish $appName --build remote

Test

My service bus message enter image description here

result enter image description here enter image description here

Jim Xu
  • 21,610
  • 2
  • 19
  • 39
  • Thanks a lot. But when I go to the edit function bit, It only shows me the TS file apart from that, all the settings are the same as yours. It still doesn't work. – Venkata K. C. Tata Jan 08 '21 at 16:04
  • @VenkataK.C.Tata COuld you please provide a sample code. Let me try it – Jim Xu Jan 09 '21 at 01:28
  • Thank you. Apart from the fun ignore file, I don't see any difference from your updated code and mine. Can I know how did you deploy to Azure? I am using azure dev ops. I have a feeling that there is something wrong with the config there. I will share the screengrab of the FTP into function there. Just in case it is different to your one. – Venkata K. C. Tata Jan 11 '21 at 10:16
  • 2
    @VenkataK.C.Tata I use Azure function CLI command `func azure functionapp publish $appName --build remote` : https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=windows%2Ccsharp%2Cbash#project-file-deployment – Jim Xu Jan 11 '21 at 12:59
  • 1
    Thanks a lot. I installed the Azure CLI tools and the Az func tools and did the remote build command. For the first time, it works. For some reason, I thought the issue was with the code but looks like, the CICD is doing something different. I can't thank you enough. – Venkata K. C. Tata Jan 11 '21 at 16:05
  • 1
    @JimXu : Thanks for this. I have a similar use case and followed the instructions. But I'm getting the following error : Exception while executing function: Functions.linuxpdf1 <--- Result: Failure Exception: Could not find Chromium (rev. 1069273). This can occur if either 1. you did not perform an installation before running the script (e.g. `npm install`) or 2. your cache path is incorrectly configured (which is: /home/.cache/puppeteer). Am I missing something here? How is puppeteer installed into Azure when I add node_modules to the .funcignore thereby removing it from my deployment? – Anupam Chand Jan 09 '23 at 05:30
  • @AnupamChand, I had the same issue - added the .puppeteerrc.cjs with the `cacheDirectory: join(__dirname, ".cache", "puppeteer")` and it helped. – Przemek Marcinkiewicz Mar 22 '23 at 02:57
4

2023 update to @JimXu's response:

We now also need to specify a location where puppeteer will be downloaded by creating a .puppeteerrc.cjs file:

// .puppeteerrc.cjs
const path = require('path');

module.exports = {
  cacheDirectory: path.join(__dirname, '.cache', 'puppeteer')
};
0

another update for people who may struggle with remote deployment, setting ENABLE_ORYX_BUILD to 0 will solve build issues

bkam
  • 1