March 20, 2011 by Alex
On Cobot we are making extensive use of the AnyTime date picker. When it came to integration testing with Cucumber/Capybara, until recently I got away with using the Rack::Test
driver and doing
And I fill in "2010-01-01 12:45:00" for "Date"
Yesterday I finally needed to enter a date in a Scenarion that was using Javascript (with the Selenium
driver. With JavaScript enabled the above step doesn’t work anymore because when Selenium tries to type into the text field Anytime pops up and resets the text field’s contents. After some fiddling around I decided that I would make selenium click the actual buttons on the date picker. Not only did this seem to be the least hackish way, it would also mean I would be testing the real thing, i.e. clicking on buttons like a user would. This becomes especially important once you start with internationalization and different time formats (e.g. 24h vs. 12h system), where you want to make sure AnyTime generates the proper date string.
Long story short, it took me almost a day to figure out how to do this. I played around with a lot of variations, but in the end this is what you have to do:
When /I select the time "([^"]+)" from "([^"]+)"/ do |time_string, label|
time = Time.parse(time_string)
And %Q{I fill in "" for "#{label}"}
find_field(label).click
click_on_selectors ".AnyTime-btn:visible:contains(#{time.year})",
".AnyTime-mon#{time.month}-btn:visible",
".AnyTime-dom-btn:contains(#{time.day}):visible:first",
".AnyTime-hr#{time.hour}-btn:visible",
".AnyTime-x-btn:visible"
end
def click_on_selectors(*selectors)
def recurse(*selectors)
if selectors.any?
wait_for_css_selector_fn(selectors.first,
"$('#{escape_javascript selectors.first}').click(); #{recurse(*selectors[1..-1])}")
else
'window.__capybara_wait = false;'
end
end
page.evaluate_script "window.__capybara_wait = true"
page.evaluate_script recurse(*selectors)
wait_until 10 do
page.evaluate_script "!window.__capybara_wait"
end
end
include ActionView::Helpers::JavaScriptHelper
def wait_for_css_selector_fn(selector, after)
<<-JS
(function() {
var time = new Date().getTime();
var runDelayed = function() {
if(!$('#{escape_javascript selector}').length) {
if(time < new Date().getTime() - 5000) {
throw('waited too long for #{escape_javascript selector}')
} else {
window.setTimeout(runDelayed, 100);
}
} else {
#{after};
};
}
window.setTimeout(runDelayed, 100);
})();
JS
end
In you Cucumber feature you can then call:
When I fill in the time "2010-01-01 12:00" for "Date"
What the above code essentially does is open the date picker and click on all the required buttons. Just as important (and that was the tricky part) it asynchronously waits for the necessary events (open/close date picker, date shows up in text field). Enjoy.