Protractor Testing with Google Map Markers and Markerclusterers, Part 3

In this final part of this post, we will be locating the cluster markers (from the  Markerclusterer or MarkerclustererPlus library) in a Google map. This is in the context of e2e testing for an AngularJS web application using Protractor.

The first part of the post was a brief introduction , while part 2 showed how to locate Google maps markers.

Markerclusterer

The cluster markers have this DOM structure.

<div class="cluster">
    <img />
    <div>10</div>
</div>

Once again it is just a case of finder the right xpath expression to use as the locator. If we were only interested in the number of cluster markers on the map, we could just use the count() utility method for the ElementArrayFinder as we did for getting a count of the Google maps markers.

element.all(By.xpath("//div[@class=\"cluster\"]/div")).count();

However for the application I was working on, I needed the total number of markers represented by all the cluster markers.

The cluster marker div structure has an inner div that contains a number, this is the number of Google map markers that are not shown but are represented by the cluster. So here we have to get this number from all the cluster markers and add them up.

ElementArrayFinder has various functions such as each() and map() that would allow us to either iterate through the cluster markers and extract the information we need so that we can total the numbers. Luckily it also has a reduce() function that does exactly what we need, and here is an example of a spec test using it.

var checkClusterNumberCount = function(expectedCount) {
    element.all(By.xpath("//div[@class=\"cluster\"]/div")).reduce(function(accum, elem) {
    return elem.getText().then(function(text) {
    var num = parseInt(text);
    return accum + num;
  });
}, 0).then(function(result) {
    expect(result).toEqual(expectedCount);
  });
};

The explanation for this function:

1. The xpath expression is used to locate all the cluster markers in the DOM.

By.xpath(“//div[@class=\”cluster\”]/div”)

2. The function element.all() returns an ElementArrayFinder and the reduce() function is called. In this case it is passed our own reduce function that has 2 parameters:

  • accum holds the accumulated number, which is initially set to zero
  • elem is an ElementFinder from the ElementArrayFinder

3. For each ElementFinder we call the getText() to get the value from the inner div. This value is converted to a number and added to the accumulated number. Notice that since ElementFinder is also a promise, we need to use another callback from the getText() function.

4. We then use the expect() function to compare the final accumulated number to the number that we were expecting in order to pass the test.

Final Tip

Initially after I ran the code to count the markers (both the displayed markers and the ones contained in the cluster markers) I found that, although the code was working properly, some of the tests would occassionally fail. Eventually I worked out that even though in the callbacks the AngularJS code may have finished running, I still sometimes needed to wait for markers and markerclusterers to finish loading in the map.

I worked around this by added a small delay before trying to locate the markers, e.g.
browser.sleep(…);

This is a bit of a hack, but unfortunately I’m not aware of any way to get a notification from Google maps when markers have finished loading.

Protractor Testing with Google Map Markers and Markerclusterers, Part 2

Part 1 of this post was a brief introduction about the Protractor spec I was working on, where I had to locate markers and cluster markers in a Google map . In this second part, there are some tips on how to find those Google maps markers.

Firstly, I must give credit to this blog post which had similar ideas about finder Google maps markers for Selenium testing.

http://tech.adstruc.com/post/34230170061/selenium-testing-google-maps

Configure the marker

Most importantly, when the marker is created it must be configured as being unoptimized. This means the markers are created as elements that can be located in the DOM.

 var marker = new google.maps.Marker({
   position: latLng,
   title: 'your title',
   optimized: false
 }

Be aware that using unoptimized markers should only be done for development and testing, as it significantly affects performance.

In the spec test we can use xpath to find the div’s that represent the markers, but the specific xpath expression will vary depending on various factors, such as whether the marker has events attached to it. For example you may want to have a click event attached to the markers, so that something happens when the user clicks on the them.

google.maps.event.addListener(marker, 'click', function() {
  // do something
});

Another factor that affects how the DOM structure for a marker is rendered, is the platform and browser that the web page with the map is running on.

The best way to formulate the xpath expression you want to use as the locator for the markers, is to use a web inspection tool to have a look at the DOM element(s) for the marker. This should be done for the browsers and platforms that you want to support.

Markers without Map Areas

The first marker DOM structure has a div that looks like this.

<div title="your title" class="gmnoprint">
  <img />
</div>

Some examples of situations where the markers that have this structure include:

  • Chrome (Windows), markers without events
  • Firefox (Windows), markers without events
  • Chrome (Android), markers with events

In the example I’m using for this post, the test spec is locating the markers in order to get a count of all the markers in a map.

element.all(By.xpath("//div[@class=\"gmnoprint\" and @title]")).count();

Several things to note here:

1. The xpath expression is used to locate all the markers in the DOM.

By.xpath(“//div[@class=\”gmnoprint\” and @title]”)

2. The function element.all() returns an ElementArrayFinder and has various utility methods such as count().

3. Since ElementArrayFinder is a promise, if you need to get values from the marker elements you need to use a callback.

For instance if you wanted to get the titles from the markers:

var titles = element.all(By.xpath("//div[@class=\"gmnoprint\" and @title]")).map(function(elem, index) {
  return {
    index : index,
    title : elem.getAttribute('title')
  };
});

Markers with Map Areas

Another marker structure is where the div contains an <area> tag inside a <map> tag.

<div class="gmnoprint">
  <img />
  <map>
    <area title="your title" />
  </map>
</div>

You may encounter this DOM structure in the following:

  • Chrome (Windows), markers with events
  • Firefox (Windows), markers with events

Once again we can get a count of the markers using a xpath expression to match the DOM structure for these markers.

element.all(By.xpath("//div[@class=\"gmnoprint\"]/map/area[@title]")).count();

In the final part of this post, I will show how to find the cluster markers in the map, and also how to find the number of Google maps markers represented by each cluster.

Protractor Testing with Google Map Markers and Markerclusterers, Part 1

While doing e2e testing on an AngularJS app using Protractor, I came across the need to find the markers in a google map within the app. This was further complicated by the fact that we were using MarkerclustererPlus, which meant on the map there could be a mixture of single markers and cluster markers.

This first part is just a bit of an introduction, so if you want, you can go straight to Part 2 which shows how to find the Google Maps markers in a Protractor spec or Part 3 for using markerclusterer.

What is a Markerclusterer?

If you are not familiar with markerclusterers, it is a google maps utility library which deals with maps that have too many markers or are too cluttered, by combining markers that are close together into cluster markers. Have a look at the example page:

http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/docs/examples.html

Also note there are actually 2 libraries, markerclusterer and markerclustererplus.

I’m assuming that the reader is already familiar with setting up and using Protractor for testing AngularJS applications.

AngularJS and Google Maps

Being an AngularJS app, I decided to use a directive to create the Google map.

There are a few AngularJS map directive libraries around, but the one I decided upon was ng-map.  The advantage of this particular library was that although you can just use its tags to create the map, it also allows you to use the Google Maps V3 Javascript API directly. This is very useful, for instance even though there was a markerclusterer tag, I just wrote the code in Javascript which was more flexible and easier to debug (the author of the library also seems to recommend this approach for complicated code).

So for this application, the map libraries that were used were:

Count the Markers …

The spec file I was working on needed to count the number of markers that were displayed on the map. Now as I mentioned earlier, because I was using the markerclusterer library, the markers could appear as single markers (i.e. the default Google Maps markers) or as cluster markers, and the number of markers and clusters would vary depending on the zoom level of the map.

Therefore in the tests I needed code to find:

  • single markers
  • cluster markers
  • the number of markers contained in each cluster marker

In the next post, I will show how to find the single markers displayed on the map. All you will need are web tools that can inspect elements in a web page, such as Firebug or the Chrome Developer Tools.