Table of contents
What The Problem Is
If you've tried to do Selenium testing of a
website that uses a lot of javascript, you may have noticed that
it's rather tricky, and Ajax just makes it worse. You'll be
regularly hit with tests that should work, but don't seem to, and
you don't know why.
There are many, many posts about various aspects of these problems,
but I've yet to see a coherent guide to the issue as a whole, so I'm
writing this, and we'll see how it goes.
You'll know you've hit a javascript problem when you can't seem
to get the test to continue to the next step; you can't get that
dropdown to activate, or whatever.
You'll know you've hit an ajax problem when your test keeps firing
off too early, and you keep putting "sleep" commands in to make the
timing right, except then the server is really overloaded one day
and they all fail and everything explodes.
Why The Problem Occurs: Javascript Events
Javascript has a collection of events. Basically, places you can
hook code to; "when X happens to Y, do Z".
In a modern browser, for example, you can put
onClick="alert('foo')"
into basically any html element:
<p onClick="alert('foo')">blah blah blah</p>
When the user clicks in the element, an alert will pop up. So far,
no problem.
The problem is that javascript has a *bunch* of these event types:
click, mouseover, focus, mouseup, mousedown, ...
Selenium does basically everything that it does by forcing
javascript events to kick off. It uses javascript events to drive
behavior inside the browser.
When you just need to click a basic html button, no big deal; simulate a click event.
But when the page itself uses javascript, it starts to matter,
because what if the javascript isn't onClick, but rather onMouseOver
? Or onMouseDown ? If you just send a click event, neither of those
get activated at all.
So you end up playing hunt-the-event-type.
General Solutions: Javascript Events
Event Type Options
A friend found me a list of javascript event types
and which HTML elements they make sense for.
This narrows down the search a lot. At the time, we were having trouble with a "select" element that was having magic Ajax-y things done to fill in its options, and that page made it obvious that the javascript that was altering the select element had to either trigger on "focus" or "change".
HTML Dumps
Probably the most useful debugging tool for a Selenium script here is getHTMLSource. At the point in your script where you can't figure out what HTML element to click or where it is in an xpath sense or what event to use to trigger it, sleep a long time (like 30 seconds), and dump the HTML source to a file. See if you can find your element in there, since that's what Selenium is actually seeing.
Why The Problem Occurs: Ajax
Ajax is a means of loading parts of a page separately from the page
as a whole. The problem is that normal web transactions are based
on the whole page, so you'll end up thinking everything is reading
when parts of the page are still loading.
General Solutions: Ajax
This one is actually fairly easy to fix once you've gotten used to
it, and consists of two basic steps:
- When doing the initial testing of the tests, put in "sleep" statements wherever necessary
- When the test script is stable, replace all the sleep commands with a waitFor of some kind
For the second part, usually waitForTextPresent or waitForElementPresent will do what you want. All you need to know
is a piece of text or an html element (respectively) that will be on the page after everything is done loading.
Some ajax libraries will say "loaded" or something, you can wait on that.
More generally, you can do something that always works for your ajax library. For some, this will be a waitForCondition
that waits until a "Loading..." text is gone, or similar; you'll have to write a bit of javascript for that.
Most ajax libraries have an actual value you can query that will tell you when everything is done, though.
Here's an excellent post that has per-library reccomendations about
that sort of thing.
Specific Solutions: jQuery Autocompletion
This isn't my solution; I found a blog post about it.
The issue is that jquery autocompletion creates the options live as you type, and it puts them in a totally different part of the HTML file; they're actually rooted at the body.
The xpaths shown there were not the ones that we saw when we did our HTML dump, which might be a jquery versioning issue. Ours were //body/div...FIXME
Specific Solutions: Magic select Element
A "select" element was being filled with "option" values only when you clicked on it, live, by a rails helper FIXME: name. We couldn't figure out how to activate the option-filling behavior until we looked at the javascript event types page linked above, and saw that "focus" was a likely option, did a Selenium focus call, and it all worked great.