jejacks0n / teaspoon

Teaspoon: Javascript test runner for Rails. Use Selenium, BrowserStack, or PhantomJS.
1.43k stars 243 forks source link

Images w/CLI Test #110

Closed andyl closed 10 years ago

andyl commented 10 years ago

These tests work in the browser, but not in the CLI using PhantomJS. (the image height/width are reported to be '0') Any ideas how to configure the CLI to make this work?

describe "Loading Image and Reading Attributes", ->
  it "loads an image from an element", ->
    el = $("<img src='https://www.google.com/images/srpr/logo4w.png'/>")
    $(document.body).append(el)
    el.show()
    expect(el.height()).toBe 190
    expect(el.width()).toBe 550
  it "loads an image from a fixture", ->
    # with <img id='testImg' src='https://www.google.com/images/srpr/logo4w.png'/>
    loadFixtures('test1.html')  
    expect($('#testImg').height()).toBe 190
    expect($('#testImg').width()).toBe 550

I saw that phantomjs has a configuration option load_inline_images which defaults to true. Perhaps teaspoon has disabled this option? Other ideas?!?

jedschneider commented 10 years ago

i think you have a timing issue here. this looks like jasmine, so I'll show you the jasmine api for this, but if its mocha, just double check the language for this or with mocha, set it up as an asynchronous test.

el = $("<img src='https://www.google.com/images/srpr/logo4w.png'/>")
runs ->
  $(document.body).append(el)
  el.show()
waitsfor ->
  $(el).is(':visible')
runs ->
  expect(el.height()).toBe 190
  expect(el.width()).toBe 550
jejacks0n commented 10 years ago

I expect Jed is correct. I've found that Phantom is pretty dang close to safari -- so another option is to open it up in safari and see if it passes there (and I suspect it will not).

jejacks0n commented 10 years ago

To clarify a little more about what Jed explained.. What you're probably seeing here is local caching. You've probably cached this image locally in your browser so it loads fast enough for this to pass, but PhantomJS being more for testing doesn't have caching like that enabled.

jedschneider commented 10 years ago

sidenotes: any time you modify state of the dom, you should probably write your specs to handle them as async tests. they will be more stable that way.

also it probably is not advisable to write tests to expect specific sizes of elements. if you really feel compelled to test this, maybe just checking visibility will keep you specs more resilient to change. expect(el).toBeVisible()

Further, you might just spy on whatever action fires the visibility and test that that action fires when the desired action happens. That way you don't actually care about dom manipulations.

class Gymnastics
  constructor: (el)->
    @el = el
  makeItShow: (event)->
    @el.show() # make dom manipulation events in their own function so you can spy
  handstandAndHoldIt: ->
    @makeItShow()

it 'becomes visible when i do a handstand', ->
  el = new Gymnastics $("<img src='https://www.google.com/images/srpr/logo4w.png'/>")
  spyOn(el, 'makeItShow')
  el.handstandAndHoldIt()
  expect(spy).toHaveBeenCalled()
andyl commented 10 years ago

@jejacks0n @jedschneider - thanks a lot for taking the time to explain the async technique. I made the changes you suggest and everything is working perfectly.

andyl commented 10 years ago

One last comment - I converted all of my image files to data-encoded URI's. This way the images are embedded directly in the tests, and do not have to be retrieved from a webserver.

I'm not sure if this is best-practice - I suppose some sort of mocking/stubbing probably would be. But for my purposes the data-encoded images were very handy.

Here is a gist with my test images. Hopefully this will be helpful to someone who stumbles across this post in the future.

jedschneider commented 10 years ago

FWIW, personally, I'd have a really hard justifying maintaining tests that validate the properties of an image (your original test). Imagine that the viewport is set for a mobile device. Is you spec still valid? Probably not. The image probably is proportional to the viewport. Just an example of how that spec provides low value to maintenance time. Again, from a personal take on it (and I've written a lot of valueless tests I wish I hand't) what you want to test is the interactions; eg when I click this, this image shows up. Whether that image is cached, encoded, or fetched on demand from the server, changing the DOM is an asynchronous process. As nice as it would be to treat it synchronously, its not. So I'd still recommend that you write such a test in the runs, waits, runs style even if the images are encoded.

But expanding on what I mentioned in my sidenotes, writing such a test is not a unit level test, its more of an integration level test. Refer back to my Gymnastics class example. You want to isolate DOM changing events into methods that can be mocked and then assert the method is called. Testing the DOM manipulation (jQuery#show in this case) is testing jQuery, the same as writing to a database for an AcitveRecord model test is testing Rails.

andyl commented 10 years ago

I agree that in most cases I would not deal with image properties. But I am writing an image editing component that does cropping, rotating, resizing etc. So in this situation the image properties are important!

Also - the editing component relies on javascript objects that perform the image manipulation calculations. I've got a suite of unit tests that work on the javascript objects, then some integration tests that use the images and exercise the whole stack. I think my technique is similar to what you recommend, except that I'm not yet using mocking/stubbing.