21

Is there a way to test a Chrome extension using Puppeteer? For example can an extension detect that Chrome was launched in "test" mode to provide different UI, check content scripts are working, etc?

ted
  • 13,596
  • 9
  • 65
  • 107
ebidel
  • 23,921
  • 3
  • 63
  • 76

1 Answers1

28

Passing --user-agent in puppeteer.launch() is a useful way to override the browser's UA with a custom value. Then, your extension can read back navigator.userAgent in its background page and identify that Chrome was launched with Puppeteer. At that point, you can provide different code paths for testing the crx vs. normal operation.

puppeteer_script.js

const puppeteer = require('puppeteer');

const CRX_PATH = '/path/to/crx/folder/';

puppeteer.launch({
  headless: false, // extensions only supported in full chrome.
  args: [
    `--disable-extensions-except=${CRX_PATH}`,
    `--load-extension=${CRX_PATH}`,
    '--user-agent=PuppeteerAgent'
  ]
}).then(async browser => {
  // ... do some testing ...
  await browser.close();
});

Extension background.js

chrome.runtime.onInstalled.addListener(details => {
  console.log(navigator.userAgent); // "PuppeteerAgent"
});

Alternatively, if you wanted to preserve the browser's original UA string, it gets tricky.

  1. Launch Chrome and create a blank page in Puppeteer.
  2. Set its title to a custom name.
  3. Detect the tab's title update in your background script.
  4. Set a global flag to reuse later.

background.js

let LAUNCHED_BY_PUPPETEER = false; // reuse in other parts of your crx as needed.

chrome.tabs.onUpdated.addListener((tabId, info, tab) => {
  if (!LAUNCHED_BY_PUPPETEER && tab.title.includes('PuppeteerAgent')) {
    chrome.tabs.remove(tabId);
    LAUNCHED_BY_PUPPETEER = true;
  }
});

puppeteer_script.js

const puppeteer = require('puppeteer');

const CRX_PATH = '/path/to/crx/folder/';

puppeteer.launch({
  headless: false, // extensions only supported in full chrome.
  args: [
    `--disable-extensions-except=${CRX_PATH}`,
    `--load-extension=${CRX_PATH}`,
  ]
}).then(async browser => {
  const page = await browser.newPage();
  await page.evaluate("document.title = 'PuppeteerAgent'");

  // ... do some testing ...

  await browser.close();
});

Note: The downside is that this approach requires the "tabs" permission in manifest.json.


Testing an extension page

Let's say you wanted to test your popup page UI? One way to do that would be to navigate to its chrome-extension:// URL directly, then use puppeteer to do the UI testing:

// Can we navigate to a chrome-extension page? YES!
const page = await browser.newPage();
await page.goto('chrome-extension://ipfiboohojhbonenbbppflmpfkakjhed/popup.html');
// click buttons, test UI elements, etc.

To create a stable extension id for testing, check out: https://stackoverflow.com/a/23877974/274673

Community
  • 1
  • 1
ebidel
  • 23,921
  • 3
  • 63
  • 76
  • 2
    I mistakenly thought CRX_PATH pointed to a .crx archive of the extension to load, but it is the folder where your `manifest.json` resides. – Eric Majerus Jun 10 '18 at 22:26
  • Also to note, navigating to the `chrome-extension://` URL can only be done after `--disable-extensions-except` and `--load-extension` args are added, as described in this answer. – Eric Majerus Jun 10 '18 at 22:27
  • 6
    What if you just want to click the extension button? – Sachi Jun 19 '18 at 19:30
  • This isn't initially clear from the above answer and comments (even though they do state it) but you don't need to create a CRX file for a local extension you're working on. Instead, you can point the CRX_PATH at the folder you're working from, just like @EricMajerus suggests. That means the same folder you would side-load into Chrome using the Load Unpacked method. So for me, I ended up adding `const CRX_PATH = '/Users/alex/git/imdb-age';` to my setup.js file. – alex Dec 23 '19 at 23:45
  • Can you show a sample snippet of button click being executed programmatically using puppeteer on chrome extension? – Selaka Nanayakkara Jan 25 '22 at 11:46