Ninja QA

Google Maps + Drawing API - Selenium Automation

So a client I have been working with uses google maps in conjunction with google maps API & Drawing tools.
I won't go into details about who or what the client does - but in short - they need to draw shapes on a map, to mark an area that is affected by an event that may happen in the future - very vague, I know - but intentionally so.

The problem is that Selenium does not have any native support for drawing shapes or interacting with google maps.
Sure, you can click on buttons on the map GUI - but it becomes problematic when you want to draw shapes over specific locations. The problem is mapping x and y on your screen, to longitude and latitude on a geolocation map. Calculating this yourself would be too much hassle as you would have to worry about page offsets, what area of the world is centred in the map etc.

Surely there must be an easier way.

There is indeed...

In my case - I needed to examine the page source to see two things.
How the shape information was saved and how the shape information was loaded.
When I say loaded, I mean - when you arrive at the page - if there is stored data, how does it get loaded into the map.

What I discovered in my examination of the page source, was that the google api used a DomListener to push the shape data into an array called 'shapes'. This variable was however declared within a scope that I could not interact with - I didn't fancy asking the developers to make it global just so our automation could manipulate it and besides - I don't think it would have helped anyway....
So what I did was followed the 'shapes' variable through the javascript code to see where it eventually ended up. I also discovered that there was a function being used called 'Initialize' which seemed to be called when the page loaded.
Possibly the load function?


function initialize() {
        var goo = google.maps,
            map_in = new goo.Map(document.getElementById('maps'),
                                              zoom: 5,
                                              streetViewControl: false,
                                              center: new goo.LatLng(54.8667, -4.45)
            shapes = [],
            //selected_shape = null,
            drawingManager = new goo.drawing.DrawingManager({

Above you can see the shapes array.

Eventually as I followed the code - I found another DomListener being put into action - this one was listening for the user clicking the 'save button'

goo.event.addDomListener(byId('save_btn'), 'click', function () {
            //var data1 = IO.IN(shapes, false);
            var data = shapes;
            var updatedData = MergeArrays(data);

I can now see that the data from 'shapes' is being fed into the 'Save' function - lets find the Save function now.

function Save(result) {

It looks like it is grabbing an object on the screen - which has an ID of 'map_json_data' and then setting its value to be equal to the JSON value of the shape data.
Lets look for other references of #map_json_data.

Right enough - I found a function called 'Reload'

function reload() {
            var mapdata = $("#map_json_data").val();
            if (mapdata != null) {
                shapes = IO.OUT(JSON.parse(mapdata), maps);                

And this function was being called from the initialize function.
So I had come full circle.
It looks like the when the page loads - the server back end populates the #map_json_data hidden element with json data relating to the shape data that needs to be displayed - then the Initialize function is executed which reads from this hidden field and then renders the data.

In order for us to modify the shapes displayed on the map, we simply need to call the 'Save' function to store JSON data to the hidden field, and then call Initialize again - to fool the map into thinking it has just loaded.

My code ended up looking like this:

string saveShapeJS = "Save('[{\"type\":\"CIRCLE\",\"id\":null,\"radius\":139204.9220945524,\"CircleLatLng\":[42.288119312630066,11.962269477805648],\"ID\":0}]');initialize();";

It is important to note that the " symbols need to be escaped - or alternatively, you can convert your javascript string to base64 and then execute it via atob() - which should free it from having to have the quotes escaped.
This code when executed, will save that Json string to the hidden field, but then call the initialize function, which will make the map load the shape data specified.
Further more - if you then click any relevant submit button - it should store the data to your server / database back end.

See below the before and after images...



Many automation engineers I have worked with in the past have often said 'That wont be achievable with automation etc...'
In my own experience, almost anything is possible with automation, if you have the willingness to investigate and maybe bend the rules with regards to your approach to the problem. While the above code may not be clicking and dragging to generate a circle on a map, it does however test the functionality of our web-site - we are able to store a circle over a geographic area and it persists to the database.

If we want to get into real semantics - do you think Selenium is actually clicking on buttons in chrome the way a Human does? This may come as a shock - but Selenium does that via javascript manipulations through the ChromeDriver.exe.
So since we are already bending the rules - and not testing 'exactly' the same way a user of the site may, why not go a step further, if it gives you that extra bit of coverage in your testing.


Add comment