An experimental Capybara driver for headless chrome.
Why? Headless Chrome via Selenium is about 2x slower than Poltergeist, which is a bummer. How fast could it be if we cut out the middleman and talked directly to Chromedriver, or Chrome? The goal of Shimmer is to figure that out.
In our headless Chrome case, chromedriver
acts as an intermediary between the Selenium WebDriver API and Chrome's internal DevTools API implementation. It's our hypothesis that the extra abstraction layer between the WebDriver and DevTools API is contributing to the performance slowdown when switching from Poltergeist to Chrome Headless.
Shimmer bypasses the chromedriver
middleman and directly communicates over a web socket to the DevTools API (see documentation). This is a real-time, event-based socket protocol.
The DevTools API is highly experimental and subject to change. Expect it to break! We currently use the entirety of the APIs, not just the stable parts but the Tip-of-Tree master variations. Be sure to keep your Chrome updated to the latest at all times.
Much of our implementation is derived and inspired from Google's Node-based puppeteer project. Some code is also derived from the chromedriver project.
In your application's Capybara setup block (oftentimes, spec/support/capybara.rb
), add the following:
# First, register the driver
Capybara.register_driver :shimmer do |app|
Capybara::Shimmer::Driver.new(app)
end
# If you want to run shimmer for all specs
Capybara.default_driver = :shimmer
# Only run shimmer for JS-based tests
Capybara.javascript_driver = :shimmer
You can pass extended options to further configure Shimmer:
Capybara.register_driver :shimmer do |app|
Capybara::Shimmer::Driver.new(
app,
headless: true, # To run Chrome headlessly
window_width: <WIDTH_IN_PIXELS>, # Default: 1920
window_height: <HEIGHT_IN_PIXELS>, # Default: 1080
port: <DEVTOOLS_PORT>, # Default: 9222
host: <DEVTOOLS_HOST>, # Remote Chrome DevTools host (default localhost)
proxy: true # Start the browser on 9223, but open a client connection to chrome-protocol-proxy on 9222
)
end
Install chrome-protocol-proxy
to see wire traffic over the remote debugging socket.
$ go get -u github.com/wendigo/chrome-protocol-proxy
Be sure to start up the proxy in a separate window before beginning the benchmark suite.
$ chrome-protocol-proxy
(By default, the proxy listens to client connections on port 9222 and forwards traffic to the Chrome child process on port 9223)
Be sure to configure your Shimmer::Driver with the proxy: true
flag.
You can play with the driver in a console session by simply launching it with:
$ ./bin/console
This automatically instantiates a Capybara::Shimmer::Driver
at driver
in your interactive session:
[1] pry(main)> driver.visit('http://www.google.com')
[2] pry(main)> driver.find_css('input[aria-label=Search]').first.set('Bitcoin')
The console runner can also be run headlessly with the --headless
flag:
$ ./bin/console --headless
There's a simple benchmark in place...
cd benchmark
./benchmark
Which currently produces these results on my laptop:
Calculating -------------------------------------
headless_shimmer 2.023 (± 0.0%) i/s - 61.000 in 30.423677s
shimmer 0.965 (± 0.0%) i/s - 29.000 in 30.120099s
poltergeist 3.372 (± 0.0%) i/s - 101.000 in 30.041906s
chrome 0.721 (± 0.0%) i/s - 22.000 in 30.583263s
headless_chrome 0.926 (± 0.0%) i/s - 28.000 in 30.272857s
Comparison:
poltergeist: 3.4 i/s
headless_shimmer: 2.0 i/s - 1.67x slower
shimmer: 1.0 i/s - 3.50x slower
headless_chrome: 0.9 i/s - 3.64x slower
chrome: 0.7 i/s - 4.68x slower
Profiling is important to understanding the root causes of slowdowns between different drivers.
The recommended way to profile test runs is to use the profile_driver
script.
$ ./benchmark/profile_driver <NAME_OF_DRIVER>
Driver may be either headless_chrome
, chrome
, headless_shimmer
, shimmer
, or poltergeist
.
A callgrind
-compatible file will be generated in the tmp/
directory. (It can also generate hundreds of partial files to support its run - you can safely ignore those.) Recommend you use qcachegrind
to view and analyze the results.
First, install qcachegrind
:
$ brew install qcachegrind
Then open a call profile to analyze the results:
$ qcachegrind tmp/headless_shimmer.callgrind.out.42062