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.