Testing mOxie File Upload with PhantomJS

Article summary

I was recently working on a feature that allowed users to upload images to a web app. The front end was written in Ember.js and the server was Rails with paperclip for image storage. For the best user experience, we decided to try to read the file locally, display it, and send the data URI up to Rails for storage.

Unfortunately, our target platform included Internet Explorer 9.

The Problem

IE 9 does not support the FileReader API. After doing some digging around we found a great polyfill library called mOxie. mOxie allowed us to fall back on flash for file upload and file reading in IE 9.

After some manual testing, mOxie appeared to work great. However, when we started writing integration tests, we discovered that PhantomJS did not implement the addEventListener function. The mOxie FileReader shim needed addEventListener.

The solution

We tried several different things to get mOxie FileReader to play nice with PhantomJS, but all of our attempts were fruitless. We decided the only legitimate workaround would be to add a fileReaderWrapper function that could be switched out at runtime when testing.

On the client side we added a mOxie FileInput and listened for changes on that input.


fileInput = new mOxie.FileInput
accept: 'image/jpeg,image/jpg,image/png,image/gif'
browse_button: ".edit"
runtime_order: "html5,flash"
fileInput.onchange = (e) =>
file = e.target.files[0]
error = @checkForErrors(file)
if error?
@sendAction 'error', error
else
fileReaderWrapper file, (f) =>
@set("file", f)
@set("fileName", e.target.files[0].name)

On line 11 we called our wrapper function. The function took a file and a callback. Here is what the fileReaderWrapper function looked like:


window.fileReaderWrapper = (file, callback) ->
reader = new mOxie.FileReader()
reader.onload = (e) =>
fileToUpload = e.target.result
callback(fileToUpload)
reader.readAsDataURL(file)

We defined the fileReaderWrapper function on the window. We created a mOxie FileReader, listened for the onload event, and called the callback with the resulting file, which is a data URL.

This allowed us to swap out the implementation in testing. This is what our RSpec test helper looked like:



def attach_file_to_field(path, opts = {})
data = Base64.encode64(File.read(path)).gsub("\n", "")
opts[:mime_type] ||= "image/jpeg"
opts[:size] ||= File.stat(path).size
opts[:index] ||= 0
element = all(".content-item.content-image .moxie-shim input", visible: false)[opts[:index]]
page.execute_script <<-JS window.fileReaderWrapper = function(filename, callback) { callback("data:#{opts[:mime_type]};base64,#{data}"); } JS attach_file(element[:id], path) end[/code] Note the page.execute_script call on line 7. Instead of reading the file in the wrapper, we simply called the callback with an already Base64 encoded file. This bypassed the mOxie FileReader problem in IE 9.

Caveat

In order to get the tests to pass, we actually swapped out some production code at runtime. This means that the mOxie FileReader code is actually untested. We made sure our exploratory tester added some manual tests to ensure that the FileReader is acting as it is supposed to.