DEV Community

Travis
Travis

Posted on • Updated on

How to use Puppeteer in a Chrome extension

At my current job I would sometimes input client details into our CRM platform and would subsequently input these same details while setting them up on another third party platform. As such, there is a lot of copying data around and manual labor.

Automating this process via a Chrome Extension seemed like a good idea because I could also share it with non-technical colleagues that weren't comfortable with running scripts. Also at this company, Chrome is the default browser for all of the machines which run on Windows. With all the stars aligned, I managed to develop a working solution, although a bit hacky but works for my needs. This post was inspired by this thread.

Some prerequisite knowledge

  • basic understanding of Chrome Extension structure
  • basic understanding of Puppeteer

I had no prior experience working with Chrome Extensions, so I highly recommend The Coding Train's Playlist which is an excellent primer to developing them. It gave me everything I needed to get up and running.

With Puppeteer, the docs is quite thorough.

Bringing it all together

The trick is to use the bundled version of puppeteer. This link points to an old commit as this is no longer within the scope of their project. S

  1. Bundle the repository per above documentation
  2. Place it in your chrome extension folder
  3. Reference the newly bundled folder in your popup.html with something like
<script src="./puppeteer/utils/browser/puppeteer-web.js"></script>

This is the hacky part. You'll then need to take advantage of Chrome's remote debugging functionality as puppeteer-web can't start its own instance via puppeteer.launch() and can only use puppeteer.connect() to connect to an already existing chrome instance. Add --remote-debugging-port=9222 to the end of the target field of your chrome.exe short cut. To learn more about this, read here.

Once remote debugging is activated you'll be able to see the webSocketDebuggerUrl property by visiting http://localhost:9222/json/version on your browser. This is the browserWSEndpoint the connect method will invoke.

Example response when visiting http://localhost:9222/json/version

{
   "Browser": "Chrome/79.0.3945.130",
   "Protocol-Version": "1.3",
   "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
   "V8-Version": "7.9.317.33",
   "WebKit-Version": "537.36 (@e22de67c28798d98833a7137c0e22876237fc40a)",
   "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/browser/343998a5-ce82-456f-944c-a214ebb59e83"
}

You will also need to add the port address to the permissions array in the Chrome Extension's manifest.json file otherwise ajax requests won't work and you'll suffer from cors errors.

Example manifest.json file.

{
  "name": "Chrome Extension",
  "version": "0.1",
  "content_scripts": [
    {
      "matches": [
        "https://url-that-you-want-to-scrape/*"
      ],
      "js": ["content.js"]
    }
  ],
  "background": {
    "scripts": ["background.js"],
    "persistent": true
  },
  "permissions": [ "tabs" , "identity","http://localhost:9222/*"],
  "browser_action": {
    "default_title": "Chrome Extension",
    "default_popup": "popup.html"
  }
   "content_security_policy": "script-src 'self' 'unsafe-eval' https://apis.google.com/; object-src 'self'",
   "manifest_version": 2
}

Example popup.html file

<!DOCTYPE html>
<html>
  <head>
    <title>Example popup</title>
    <link rel="stylesheet" type="text/css" href="style.css">
  </head>
  <body>
    <div>
      <button id='puppeteer-button'>Do Puppeteer Things</button>
      <script src="./puppeteer/utils/browser/puppeteer-web.js"></script>
      <script type="module" src="popup.js"></script>
    </div>
  </body>
</html>

Example popup.js file

let browserWSEndpoint = '';
const puppeteer = require("puppeteer");

async function initiatePuppeteer() {
  await fetch("http://localhost:9222/json/version")
    .then(response => response.json())
    .then(function(data) {
        browserWSEndpoint = data.webSocketDebuggerUrl;
      })
    .catch(error => console.log(error));
}

initiatePuppeteer();

// Assign button to puppeteer function

document
  .getElementById("puppeteer-button")
  .addEventListener("click", doPuppeteerThings);

async function doPuppeteerThings() {
  const browser = await puppeteer.connect({
    browserWSEndpoint: browserWSEndpoint
  });
  const page = await browser.newPage();

  // Your puppeteer code goes here

}

Hopefully this is of help to someone. Happy automating.

Edit: Upon reflection, I'm pretty sure this can be accomplished with just basic javascript and dom manipulation via chrome.tabs.create and chrome.tabs.executeScript API and editing .value of input fields and what not. In my use case, it involved logging into multiple platforms and clicking various buttons through various states which may have been messy with plain javascript and I've already committed to puppeteer. Although if you just had a form that needed to be inputted and submitted, that's a different story.

Top comments (10)

Collapse
 
mattduffield profile image
Matt Duffield

Hi Travis,

I enjoyed reading your article. In looking at your manifest, see that you also have content_scripts and background but you don't provide any example as how they fit into everything. Would you mind providing a sample repo where we could look at all the moving parts?

Collapse
 
tchan profile image
Travis

Hi Matt, thanks for your comment!

I'll try editing the article when I have time, but content_script and background script are the building blocks for chrome extensions. I would recommend watching this video a better overview/primer.

Essentially, the content script allows for manipulation of the DOM and the background script, like the name implies sits in the background, handle state related things, or communicate to other aspects of your chrome extension, commonly the popup.js that is associated with the popup html when you click your chrome extension icon in the browser.

Hope that helps.

Collapse
 
mattduffield profile image
Matt Duffield

Hi Travis,

Thanks for your response. I understand about both the content_scripts and background scripts but neither are necessary for running Puppeteer in the browser. I look forward to a working repo.

I was able to get it working but I had to have two instances of Chrome open. One to interact with the extension and the other to open the new page. Not sure why though?

Also, how do you debug the popup.js when the first thing you do in Puppeteer is create a new page for your automation? The very act of launching a new page closes the popup and the ability to debug that popup.js session.

Thanks again for your response and I look forward to your working repo.

Best regards,

Matt

Thread Thread
 
tchan profile image
Travis • Edited

From memory, passing data between content, background and popup scopes was a real pain in the ass when dealing with Chrome extensions.

I believe that's one of the limitations of puppeteer-web in general is that it opens up a new web page as it only has access to the connect api as opposed to launch which can open up a brand new tab in your original instance. It looks like the link I posted initially to the official docs is 404'd :/ but I found my initial info here.

When I was debugging popup.js, I just clicked my chrome extension next to the url bar to open up the popup.html viewport, right click -> inspect and added a breakpoint to my popup.js file. That way when the new browser instance launched via puppeteer, the popup window doesn't close. Let me know if you've already tried that, sorry I couldn't be more help.

Thread Thread
 
mattduffield profile image
Matt Duffield

Okay, so it seems like everything is working as I expected. I am using Chrome Canary for my testing and it seems to be working but the debugger tools are not behaving perfectly. I can debug though.

Thanks for following up.

Collapse
 
tohodo profile image
Tommy

Awesome solution, thanks for the writeup. If you're familiar with JavaScript, you can also use an extension called Autofill to automate pretty much anything you can imagine (I have used it as a bot to automate checkout in Supreme and to win lotteries for sneaker launches).

Collapse
 
memahesh profile image
Medam Mahesh

Hi Travis,

Actually, I am facing some issues doing the same. It would be great if you could send me the final bundled version / any public repo you worked on.

Collapse
 
memahesh profile image
Medam Mahesh

Also, this is not headless, right.

Collapse
 
trixualz420 profile image
Ethan Martin

Thank you so much, ive been struggling trying to figure out how to use puppeteer for my chrome extension and you helped me so much

Collapse
 
chiefeno profile image
chiefeno

Hey, great article! I need some help referencing the puppeteer-web bundle in the content script and background script