Dreaming of Code

Hidden Screenshots in Rails System Test Output

July 16, 2017

Uses for Action Cable beyond the Chat App

I've been having boat loads of fun finding uses for ActionCable beyond the chat app. I reinvented two player pong a while back and have recently been search for solutions of outputting terminal commands to a web page. The main purpose of this was to support test execution from the chrome extension for System Tester.

Streaming the Output Line by Line

With a bit of help from Stack Overflow, I used pty to setup a command handler for streaming the command output and created a simple helper to do so:

# app/models/command_handler.rb
require 'pty'
class CommandHandler
  attr_reader :status
  def initialize(cmd)
    @cmd = cmd
    @status = nil
  end

  # getc = each character
  def run(&block)
    status = pty do |r,w,pid|
      yield r.getc until r.eof?
      Process.wait(pid)
    end
  end

  def run_each_line(&block)
    status = pty do |r,w,pid|
      r.each do |line|
        yield line
      end
      Process.wait(pid)
    end
  end

  private

  def pty(&block)
    PTY.spawn(@cmd, &block)
    @status = $?.exitstatus
  end
end

Action Cable

With the command helper in place we can now broadcast each line to an appropriate channel and subscribe to it on the frontend. To get colors to show up, it is simply a matter of replacing the appropriate characters.

# broadcast the output
command_handler = CommandHandler.new("bin/rails test:system")
status = command_handler.run_each_line do |line|
  ActionCable.server.broadcast "output", {output: line}
end
// handle on the client side
App.output = App.cable.subscriptions.create("OutputChannel", {
    received: function(data) {
        var status = document.getElementById('status');
        status.innerHTML = 'running';

        var code = document.getElementById('output');
        code.innerHTML += data.output
            .replace(/\[33m/g, '<span style="color:yellow;">')
            .replace(/\[32m/g, '<span style="color:green;">')
            .replace(/\[31m/g, '<span style="color:red;">')
            .replace(/\[0m/g, '</span>');
    }
});

The Discovery

The Command Handler has two methods for outputting by each character or line by line. I was originally hoping for a more real time output and went with the character by character. The order in which the ActionCable broadcasts were received on the client side were unpredictable, so I also had to keep track of position. I imaginge this was due to the fact of using the default async background handler. In the end I decided to go with line by line. I had already been familiar of how to get colors to show in bash terminals and was originally just running the test command without any system tests in place. When I created and ran a failed system test using the output broadcaster I noticed an extra line in there.

Rails Hidden Screenshots

Perfect! I had originally planned to implement a way of viewing screenshots in System Tester. Now all I had to do was scrape it from the output.

// improved to handle images
App.output = App.cable.subscriptions.create("OutputChannel", {
    received: function(data) {
        var status = document.getElementById('status');
        status.innerHTML = 'running';
        var screenshot = document.getElementById('screenshot');
        var code = document.getElementById('output');
        if (data.output.includes(']1337;')) {
            var splitImg = data.output.split(';');
            // Get the title
            var imgTitle = atob(splitImg[1].split('=name=')[1]);
            // Get the height
            var height = splitImg[2].split('=')[1];
            // Get the Data
            var imgData = 'data:image/png;base64,' + splitImg[3].split(':')[1];
            screenshot.innerHTML += '<p>' + imgTitle + '</p>';
            screenshot.innerHTML += '<img src="' + imgData + '" alt="' + imgTitle + '" height="' + height + '">';
        } else {
            code.innerHTML += data.output
                .replace(/\[33m/g, '<span style="color:yellow;">')
                .replace(/\[32m/g, '<span style="color:green;">')
                .replace(/\[31m/g, '<span style="color:red;">')
                .replace(/\[0m/g, '</span>');
        }
    }
});

And that is that. I hope this was useful for someone out there as it was for me. If there are any projects out there who haven't started using ActionCable, a messaging or chat application is not the only use case so see what you can do with it.

Demo App: https://github.com/rlafranchi/rails_terminal