Headless Firefox in Node.js with selenium-webdriver

As of version 56 (currently in Beta), Firefox supports running headlessly on Windows, macOS, and Linux. Brendan Dahl has previously described how to use SlimerJS to drive headless Firefox. You can also drive it via the W3C WebDriver API, and this blog post explains how to do that in Node.js with the selenium-webdriver package.

(For a similar introduction using Python on Windows, see Andre Perunicic’s Using Selenium with Headless Firefox.)

First, ensure you have a version of Firefox that supports headless. On Linux, the current release version (55) is sufficient. On Windows and macOS, however, you’ll need at least version 56, which is currently in Beta (scheduled for release next month). You can also use Developer Edition (based on Beta) or a Nightly build; any pre-release build will do.

Next, install geckodriver (and ensure it’s on your PATH). You can download and install it manually from the geckodriver releases page, or you can install it using NPM via npm install -g geckodriver or yarn global add geckodriver (mind node-geckodriver #30 on Windows). On macOS, you can also use Homebrew to install it via brew install geckodriver.

Finally, create a Node project, initializing it with your favorite package management tool and installing the selenium-webdriver package:

mkdir project-dir
cd project-dir
npm --yes init # yarn --yes init
npm install selenium-webdriver # yarn add selenium-webdriver

Now you’re ready to drive headless Firefox from Node scripts in your project.

For example, here’s how to create a script that searches for “testing” on the Mozilla Developer Network and takes a screenshot of the result. It uses features available only in Node 8, but scroll to the bottom for a reference to the equivalent for Node 6.

First, import some useful core Node methods:

const { writeFile } = require('fs');
const { promisify } = require('util');

Then import APIs from selenium-webdriver and selenium-driver/firefox:

const { Builder, By, Key, promise, until } = require('selenium-webdriver');
const firefox = require('selenium-webdriver/firefox');

Tell selenium-webdriver to disable its “promise manager” so we can use Node’s native async/await (which will become unnecessary when the promise manager is removed in selenium-webdriver #2969):

promise.USE_PROMISE_MANAGER = false;

Then create a Binary instance:

const binary = new firefox.Binary();

On Windows and macOS, if you have multiple versions of Firefox installed, configure it with the distribution channel (NIGHTLY, AURORA, BETA) to ensure you get the correct one:

const binary = new firefox.Binary(firefox.Channel.NIGHTLY);

On Linux, if you’d like to use a different version of Firefox than the one on your PATH, specify the path to the executable:

const binary = new firefox.Binary('/path/to/firefox');

Add the --headless argument to the binary:

binary.addArguments("--headless");

(Eventually selenium-webdriver #4591 will make this a driver configuration option.)

Then start Firefox with the Binary you previously created:

const driver = new Builder()
.forBrowser('firefox')
.setFirefoxOptions(new firefox.Options().setBinary(binary))
.build();

Finally, tell Firefox to load the Mozilla Developer Network home page, enter “testing” into its search form, hit the RETURN key to submit the form, await loading of the search results page, take a screenshot of the page, and save the screenshot data to a screenshot.png file in your current directory:

async function main() {
  await driver.get('https://developer.mozilla.org/');
  await driver.findElement(By.id('home-q')).sendKeys('testing', Key.RETURN);
  await driver.wait(until.titleIs('Search Results for "testing" | MDN'));
  await driver.wait(async () => {
    const readyState = await driver.executeScript('return document.readyState');
    return readyState === 'complete';
  });
  const data = await driver.takeScreenshot();
  await promisify(writeFile)('screenshot.png', data, 'base64');
  await driver.quit();
}

main();

That’s it!

For the complete script, along with a version that works on Node 6, see the headless-examples repository on GitHub. And for additional information on using selenium-webdriver, see the selenium-webdriver README, the API documentation, and this directory of example scripts.

Note: Updated on 2017 September 1 to specify headless mode using the --headless command-line argument rather than the MOZ_HEADLESS=1 environment variable.

 

Myk Melez

Myk is a Principal Software Architect and in-house entrepreneur at Mozilla. A Mozillian since 1999, he's contributed to the Web App Developer Initiative, PluotSorbet, Open Web Apps, Firefox OS Simulator, Jetpack, Raindrop, Snowl, Personas, Firefox, Thunderbird, and Bugzilla. He's just a cook. He's all out of bubblegum.

 

6 thoughts on “Headless Firefox in Node.js with selenium-webdriver

  1. Good example, thanks. But it doesn’t works inside Docker: WebDriverError: Process unexpectedly closed with status: 255

    Testing inside KVM passed with no errors.

  2. Hi Myk,
    Thanks a lot for this article. Fyi, I got an error on “const binary = new firefox.Binary();” ( firefox.Binary is not a constructor) with my node v8.9.4.
    I used options to fix the issue:

    var firefoxOptions = new firefox.Options();
    firefoxOptions.setBinary(‘/path/to/binary’);
    firefoxOptions.headless();
    const driver = new Builder()
    .forBrowser(‘firefox’)
    .setFirefoxOptions(firefoxOptions)
    .build();

    1. That seems more like a selenium-webdriver version issue than a Node version issue. I can’t reproduce it on my own machine running selenium-webdriver 3.5.0 and 3.6.0 with Node 8.9.4 and 9.8.0. I recommend checking the version of selenium-webdriver you have installed and updating it if needed.

Leave a Reply

Your email address will not be published.