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.


Originally published at Mykzilla.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.