The other day I’ve stumbled upon an interesting challenge. I had to write a feature test for a pin showing up on Google Maps. So basically the feature was supposed to check that when a user visits a particular page, he would be able to see a pin on a Google map.
Initially I thought this should be pretty easy to implement, I would just do a find using the right DOM selector and that’s that. So let’s see how that worked out.
First, I’ve added a cucumber feature test so I could check on how I’m doing as I go.
@javascript
Feature: Maps pin
Scenario: User sees one pin on the map
Given I am on the homepage
Then I should see a pin on the map
Then, I’ve added those steps in. What I wanted here, as the expectation, was to be able to say “I should have this many marker images on the page”.
Given(/^I am on the homepage$/) do
visit root_path
end
Then(/^I should see a pin on the map$/) do
expect(page).to have_selector('#markers img', count: 1)
end
And here’s how our html and javascript code looks like. Nothing fancy here, I just define a DOM element with an id of map
so we can reference it from the javascript side and add the map to it.
<div style="width: 800px;">
<div id="map" style="width: 800px; height: 400px;"></div>
</div>
And so on the javascript side, the way you add the map looks like the following (you can check out the Google Maps API docs if you want to read more about how the API is used)
$(document).ready(function () {
handler = Gmaps.build("Google");
handler.buildMap(
{
provider: {
disableDefaultUI: true
},
internal: {
id: "map"
}
},
function () {
markers = handler.addMarkers([
{
lat: 45.7554626,
lng: 21.2240976
}
]);
handler.bounds.extendWith(markers);
handler.map.centerOn({
lat: 45.7554626,
lng: 21.2240976
});
handler.getMap().setZoom(13);
}
);
});
As expected, that test would fail because there is no #markers
element yet. So the next obvious step would be to check out the DOM tree and see how the map looks before figuring out where to put the #markers
wrapper.
After opening up the DOM inspector and looking around the DOM tree for a few seconds, I realised the whole thing is a canvas on which Google Maps draws the entire map. There’s just an empty div
that holds the entire map, no tags for the map pin like I would’ve expected.
Now that was a show stopper, because I didn’t have anything to check against. So what now?
How Can You See the Markers in the Dom Tree
The good news is that Google Maps provides a way to add the marker tags back into the DOM by using the optimized: false
option. This option though, makes the map rendering less efficient so you wouldn’t want to use it in production. For that reason, I’m only adding that to the test environment.
<div style="width: 800px;">
<div
id="map"
style="width: 800px; height: 400px;"
data-test-env="<%= Rails.env.test? %>"
></div>
</div>
That sets the test-env
data attribute to true
if the environment is test
and false
otherwise.
$(document).ready(function () {
var mapEl = $("#map");
var optimized = !mapEl.data("test-env");
handler = Gmaps.build("Google");
handler.buildMap(
{
provider: {
disableDefaultUI: true
},
internal: {
id: "map"
}
},
function () {
markers = handler.addMarkers(
[
{
lat: 45.7554626,
lng: 21.2240976
}
],
{ optimized: optimized }
); // It's true or false
handler.bounds.extendWith(markers);
handler.map.centerOn({
lat: 45.7554626,
lng: 21.2240976
});
handler.getMap().setZoom(13);
if (!optimized) {
// We're adding a container for the markers.
// That way we can find all the markers by
// doing $("#markers img").
var myoverlay = new google.maps.OverlayView();
myoverlay.draw = function () {
this.getPanes().markerLayer.id = "markers";
};
myoverlay.setMap(handler.getMap());
}
}
);
});
Notice the { optimized: optimized }
option, added to the markers. That options is passed to the javascript side via the data-test-env
attribute that I’ve set in the html. I now had my markers showing up on in the DOM so I could test their presence in my cucumber steps.
But there’s one gotcha with that. Google Maps also generates an image for all the controls on the map and whatnot so you can’t just look for an image tag, you need to somehow constrain the image tags to a wrapper selector.
That’s what the google.maps.OverlayView
section does. It wraps just the pins in a #markers
selector so we can satisfy our #markers
img expectation.
Conclusion
Whenever you need to write tests against a Google Map, consider using the optimized: false
option when adding the markers to the map. That way you will have something to test against.
If you want to see the code for this example, I’ve set up a Rails application up on GitHub at https://github.com/mixandgo/blog_gmaps_selenium