Are you looking for a way to install PhantomJS automatically on program start? This ProTip is for you.

Consider the following program titles.rb. The example prints the titles of posts that appear in the main page of this blog.

#!/usr/bin/env ruby

require 'capybara/dsl'
require 'capybara/poltergeist'

include Capybara::DSL
Capybara.default_driver = :poltergeist

visit 'http://ruslanledesma.com/'
all('.post-link').each do |pl|
puts pl.text
end

The example works fine when command phantomjs is in $PATH.

ruslan$ ./titles.rb
including Capybara::DSL in the global scope is not recommended!
How to do before(:all)/after(:all) in minitest
Correole: The minimum feature newsletter in Ruby
Why does Heap's algorithm work?
How to count cycles in a complete graph
Arbitrage
Release ActiveRecord connections in Sinatra + Thin
Stacking Boxes
Ecological Bin Packing
The Blocks Problem
The 3n + 1 Problem
ProTip: Redirect stdout and stderr to console and different files in Bash
Jill Rides Again
The Captured Wise Men
The Adjacent Coins Problem

Otherwise you get this ugly error.

ruslan$ ./titles.rb
including Capybara::DSL in the global scope is not recommended!
/Users/ruslan/.rvm/gems/ruby-2.3.1/gems/cliver-0.3.2/lib/cliver/dependency.rb:143:in `raise_not_found!': Could not find an executable ["phantomjs"] on your path. (Cliver::Dependency::NotFound)
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/cliver-0.3.2/lib/cliver/dependency.rb:116:in `detect!'
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/cliver-0.3.2/lib/cliver.rb:24:in `detect!'
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/poltergeist-1.10.0/lib/capybara/poltergeist/client.rb:37:in `initialize'
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/poltergeist-1.10.0/lib/capybara/poltergeist/client.rb:14:in `new'
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/poltergeist-1.10.0/lib/capybara/poltergeist/client.rb:14:in `start'
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/poltergeist-1.10.0/lib/capybara/poltergeist/driver.rb:44:in `client'
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/poltergeist-1.10.0/lib/capybara/poltergeist/driver.rb:25:in `browser'
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/poltergeist-1.10.0/lib/capybara/poltergeist/driver.rb:97:in `visit'
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/capybara-2.7.1/lib/capybara/session.rb:233:in `visit'
from /Users/ruslan/.rvm/gems/ruby-2.3.1/gems/capybara-2.7.1/lib/capybara/dsl.rb:52:in `block (2 levels) in <module:DSL>'
from ./automagically.rb:9:in `<main>'

In this case, try the following program magic-titles.rb.

#!/usr/bin/env ruby

require 'phantomjs'
require 'capybara/dsl'
require 'capybara/poltergeist'

include Capybara::DSL
Capybara.default_driver = :poltergeist
page.driver.options[:phantomjs] = Phantomjs.path # MAGIC

visit 'http://ruslanledesma.com/'
all('.post-link').each do |pl|
puts pl.text
end

Line MAGIC installs PhantomJS in ~/phantomjs, so you might want to remove that directory afterwards. Now you get titles.

ruslan$ ./magic-titles.rb
including Capybara::DSL in the global scope is not recommended!
Phantomjs does not appear to be installed in /Users/ruslan.ledesma.garza/.phantomjs/2.1.1/darwin/bin/phantomjs, installing!
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 16.3M 100 16.3M 0 0 43736 0 0:06:32 0:06:32 --:--:-- 48617
Archive: phantomjs-2.1.1-macosx.zip
creating: phantomjs-2.1.1-macosx/
creating: phantomjs-2.1.1-macosx/bin/
...

Successfully installed phantomjs. Yay!
Removed temporarily downloaded files.
How to do before(:all)/after(:all) in minitest
Correole: The minimum feature newsletter in Ruby
Why does Heap's algorithm work?
How to count cycles in a complete graph
Arbitrage
Release ActiveRecord connections in Sinatra + Thin
Stacking Boxes
Ecological Bin Packing
The Blocks Problem
The 3n + 1 Problem
ProTip: Redirect stdout and stderr to console and different files in Bash
Jill Rides Again
The Captured Wise Men
The Adjacent Coins Problem

The elided part consists of a list files that are installed in ~/.phantomjs.

How does MAGIC work?

Line MAGIC installs PhantomJS and tells Poltergeist where to find command phantomjs.

Installation happens when MAGIC calls method Phantomjs.path. The code that does the installation is located in method Phantomjs::Platform#install!. The code installs PhantomJS in ~/.phantomjs when PhantomJS is not installed there and is not in your $PATH.

MAGIC tells Poltergeist the path of command phantomjs by setting option page.driver.options[:phantomjs] to the path given by Phantomjs.path. The option is applied when visit 'http://ruslanledesma.com' executes. The code that applies the option is located in Poltergeist.

Gems known to work together

Do’s and don’ts

Do apply this ProTip when you are doing a one-off task. Do remember to remove directory ~/phantomjs when you don’t need it anymore.

Do not apply this ProTip when installing PhantomJS by other means gives a shorter development cycle. For example, when you run you tests in a Docker container, install PhantomJS and either do not discard the container’s data or create a new image from the last state of the container. This way you will not lose time installing PhantomJS every time you run tests.

Do not apply this ProTip when there is a dedicated mechanism for installing dependencies. For example, when you need PhantomJS for a project and that project has a Gemfile, indicate that PhantomJS should be installed when dependencies for your project are installed.

Want to read more?

I love to explain and answer questions on programming problems, the kind you find in coding interviews. I publish a new programming problem and its solution every Sunday. Did I mention that I love to answer questions?

If you would like to get the latest problem + solution, subscribe to the newsletter or subscribe via RSS. You can also follow me on Twitter, GitHub, and LinkedIn.

Comments