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.

Cucumber Google Maps

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.

Google Maps Dom Tree

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