March 12, 2014

Introducing Pagelapse

I was feeling somewhat unproductive last week. Although learning Clojure has been enjoyable, I missed my efficiency in Ruby. As a break from Clojure, I built Pagelapse, a Ruby gem to create time-lapses of websites. I spent half of Wednesday and Thursday coding it, just finishing it in time for Hacker School’s Thursday presentations.

Pagelapse uses PhantomJS to take screenshots of websites, and Sinatra to host a local time-lapse viewing server. Pagelapse comes with a command line tool for easy integration with your project’s foreman Procfile. Pagelapse isn’t just for your own projects, though — you can use it with any website you’d like.

Installation & Usage

Installation is simple:

brew install phantomjs
gem install pagelapse

If you’re not on OS X, you’ll have to install PhantomJS yourself.

Once that’s installed, create a Lapsefile in the root directory of your project, or a folder where you want to keep the screenshots. Lapsefiles are just Ruby with a tiny little DSL to tell Pagelapse which sites to record. (Why does Pagelapse need it’s own DSL? For fun!)

Here’s an example Lapsefile:

record "lord", "http://www.lord.io/blog"
record "metafilter", "http://www.metafilter.com"

The first argument to record is the name of the time-lapse, and the second is the URL of the page to record. Start recording by running pagelapse in your shell with the Lapsefile in the current directory.

You can add as many record statements as you’d like. You can also specify the interval between captures with a do block, as well as the size of the virtual browser window.

record "lord-mobile", "http://www.lord.io/blog" do |r|
  r.width = 300 # pixel width of the virtual window
  r.height = 500 # pixel height of the virtual window
  r.interval = 10 # time between captures, in seconds
end

You can also run Capybara commands to take screenshots of pages that need signing in:

record "github", "http://github.com/login" do |r|
  r.interval = 10
  r.before_capture do
    page.fill_in('login', :with => 'lord')
    page.fill_in('password', :with => 'my password here')
    page.click_button('Sign in')
  end
end

There are several other parameters that you can set. Check out the readme for the complete list.

After you’ve captured a bunch of screenshots, you can view the files with the viewing server.

pagelapse view

This command starts a server at http://localhost:4567. Just click the name of the time-lapse you want to view, and then move your mouse from left to right to play it.

Lessons Learned

I’ve never built a DSL in Ruby before, so this was an educational exercise in using instance_eval with File.read. Let’s look briefly at a abridged version of parser.rb

class Parser
  def initialize(file)
    puts "Starting Pagelapse"
    instance_eval(File.read(file), file, 1)
  end

  private
  def record(name, url)
    # do stuff
  end
end

Now, when the command line tool runs Parser.new('./Lapsefile'), the Parser class will run the contents of the file as though it were written directly into the class’s initialize method, with full access to the Parser‘s public and private methods. If we used require instead of this method, the file would not be scoped in the instance, and so the Lapsefile would have to call parser.record (or something like that) instead of record. Not quite as clean.

Also, for the curious, the second and third arguments of instance_eval are for errors. Since the Lapsefile contents are passed to instance_eval as a string, if there’s an error, Ruby has no idea where to say the error came from. Those arguments tell Ruby that the string passed to instance_eval corresponds with line 1 of file. That way, Ruby can display errors during the eval as coming from the Lapsefile.

All in all, I’d call this a successful project. It was short, sweet, and I learned something. Plus, it’s a useful tool that I might use in the future.