Friday, June 28, 2013

Rails integration headless browser testing: why Ajax may destroy your will to live

You're writing a few Ruby on Rails integration/features tests, using the headless browser tool of your choice - Capybara-wekit, Poltergeist, etc. Say you write a feature test that probes a route containing buggy code. Your code base will fall over, the test will fail, and the HTML readout of your page, accessed within Capybara by going

page.html

will show a huge amorphous blob of a stack trace. Annoying to debug, but it's something. So far so good.

Now imagine you've fixed whatever that first problem was, and you're merrily testing away, writing feature tests to ensure different bits of the page behave as they should. You arrive at a form, perhaps one which communicates with the server and spits back more page content. You write the relevant bits of form general-mess-about-y code using Capybara's Action methods, write a test to ensure the page content gets updated the way you think it should and run the test. Bam.

The test fails - the page content is unchanged.

Why? The server didn't spit out a stack trace. Is there something wrong with the browser response, then? You check the Javascript console messages - a tad difficult, considering you're doing headless browser testing, but with Capybara-webkit, simply check the

page.driver.console_messages

variable, which now surely is crammed with informative messages telling you what went wrong. Nothing again. How odd. Okay then, is the response executing at all? Add a

console.log("Hey, I'm alive!");

call or two to it. Test. Nothing. I went through all this and more. I added an

ap params

to the start of the Ajax controller response, and that worked just fine, so clearly the test form submission itself was working. (And by the way, you should totally check out the fantastic gem awesome_print). I spent several hours tearing my hair out, trying to figure out why the hell code would apparently silently fail like this. Finally, of course, I realised that there are two separate controller calls here, the initial browser page load and the Ajax response, and only the first shows stack traces in response to code errors. The second just glares at you, as I found to my cost. To find the problem, you could try wrapping the entire controller method in a begin/rescue/end block, just while you're troubleshooting, and printing the exception message. Crude but effective.

1
2
3
4
5
6
7
def create
  begin
    # Your incredibly insolent Rails code goes here
  rescue e
    ap e.message # Caught you
  end
end


Wednesday, June 26, 2013

Making Capybara integration testing play nicely with jQuery Elastic

One of the coolest ways to test the hell out of your Rails app, or any Rack-based app, is Capybara - it opens an actual browser window, in your test environment, and automatically executes all the instructions you give it - going to whatever page you like, logging into your app, interacting with form elements, the lot, saving you the hassle of doing it manually every time you make the tiniest little change you think might cripple your app.

Elastic is a jQuery plugin. It dynamically expands textareas to hold their content. You know what it's like. You're typing in a textarea, and bam, you run out of space, scrollbars appear, and you have to scroll like mad to access the novel you're writing. Elastic changes all that. A hell of a useful plugin.

As I discovered over several agonizing hours recently, though, Capybara and Elastic sometimes do not play nicely together. See, when you type into an Elastic-ified textarea, it's not actually a textarea at all. It's a custom div made by the plugin. If you right-click it in Chrome and choose "Inspect Element", you might see something like this:


1
2
3
4
5
6
<div style="border: 1px solid rgb(161, 161, 161); display: none;
font-family: sans-serif; font-size: 13px; font-weight: 400; line-height: normal;
padding: 5px; position: absolute; white-space: pre-wrap; width: 470px;
word-wrap: break-word;">
Your Content Here
</div>


Okay, so what? you may ask. So you're typing into an autogenerated div instead of a textarea. Does this break the form the textarea's in? No. When you hit Submit, the plugin copies your expand-o-div's contents back into the original textarea, ensuring all is fine and dandy?

So what's the problem?

The problem comes to light when you're running Capybara tests to ensure that the textarea contains the content you think it does. Say you're putting together a comment submission form in Rails. The textarea HTML will look something like this:

1
2
3
<textarea id="comment_content" name="comment[content]">
Your Content Here
</textarea>


Somewhere in your Javascript, you'll have a line saying

$('#comment_content').elastic()


And in your integration tests, you might have something saying

page.find('textarea[name="comment[content]"]')['value'].should eq "Hey look, I've typed this after calling elastic()"


And bam. It fails.

Failure/Error
 page.find('textarea[name="comment[content]"]')['value'].should eq
"Hey look, I've typed this after calling elastic()"

expected to find text "Hey look, I've typed this after calling elastic()"
actually found "Your Content Here.". The content <i>before</i> you 
called elastic(). I know why, and I could tell you why, but you're just a
silly human, you wouldn't understand.


If you're anything like me, you can spend several agonizing hours staring, frazzled and homicidal, at this infuriating message. Or you can be slightly smarter than I was, put two and two together, and realise that, hey, you need to trigger the form's submit() method to copy your expand-o-div's content back into the original textarea. Then you're golden.