Testing an SVG game

With the web becoming an application platform for everything from word processors and spreadsheets to video editing and games, we need tools, strategies and a sound ecosystem for developing the modern web.

Developers are starting to discover the potential of web technologies such as SVG, Canvas and JavaScript as web browsers now gain wider support and better performance.

Andreas Bovens from Developer Relations at Opera approached me about whether I could come up with a demo for his talk at SVG Open this week. I must admit that I'm not an SVG expert in any regard, but of course I can't refuse a good challenge.

Since SVG is becoming such an integral part of the web I looked around at some interesting use cases of it, and decided to have a go at creating some automated top-down, end-user tests of a game. Conventiently Daniel Davis from Opera had written a game called Inbox Attack using SVG and JavaScript. Since I work a lot on browser automation, what could possibly be more fun than to investigate how to approach this problem using WebDriver?

Selenium WebDriver is a framework doing browser automation. It allows you to emulate user interaction to navigate to a URL, clicking a link, typing in text and so on. The WebDriver API is very consise and is supported by all major browsers on all operating systems. If you want to do browser testing, this is the tool you want to use.

Challenges

Caching

As they say, there are (at least) two things that are difficult in computer science: Caching, and naming things. One of the biggest challenges interacting with this game from Java is to keep your locally cached data updated.

To get the position of the player and some form of representation of where the mail is dropping from the sky, I decided to do the easiest thing and expose this data in a debugData() function. It constructs a simple JavaScript hash we convert into a Java Map type easily. It looks like this:

/**
* Fetches debug data for automation.
*
* @return a hash containing debug data
*/
function debugData() {
  return {
    state: state,
    userPosition: user.channel_id,
    newMail: newMail,
    inbox: mail,
    countCaught: count_caught,
    countMails: count_mails,
    countTotal: total_mails
  }
}

Since my implementation just stores these values in a debug object, we have no way of extracting or retreiving individual variables from the game. This means we can't query the game's representation of present mails, because the game itself has no concept of a collection of mails. The game was not written with testability in mind, but of course this could be changed quite easily.

If you write an application or a game you must always bear testability in mind. Exposing APIs for enabling the application to be tested properly will save you a lot of time later. One example from a different area is the Opera browser which exposes a number of “services” through the Scope protocol that enables, for instance, the OperaDriver implementation of WebDriver to tap directly in to the core of the web browser.

Catching mail

Another challenge is to determine how long to make the player stay in the current position to be able to catch the mail. Mail is dropping from the sky all the time and without any logic to determine for how long to wait in a certain position, the robot will move the player into the next available mail's dropping position before catching the first mail.

The way I decided to tackle this problem was to introduce three different states on each individual mail, and to store all dropped mails inside an Inbox object. This object would contain all dropped/introduced mails, regardless of whether they had been missed or caught.

UNKNOWN
The mail has been dropped and is visible, but we don't know its status yet. This is the same thing as saying it's not been caught or missed.
CAUGHT
Signifies that the mail has been caught by the player, earning her a point.
MISSED
The mail will be considered missed if the game removes the SVG object from the page.

To determine whether to move the player in to the next position we could now simply query the current unhandled (unknown) mail and not move on until its state had changed to either CAUGHT or MISSED:

for (Mail mail : game.inbox().getPresentMail()) {
  …
  if (!game.inbox().getAllMail().get(mail.getId()).isUnknown()) {
    wait = false;
  }
  …
}

Result

Apart from these to game logic-related issues the process of automating the Inbox Attack game was relatively straight forward. The first step in testing an in-browsre game like this one is to create a library wrapper for communicating with WebDriver. In my implementation this consisted of a few obvious classes:

InboxAttack
The main class responsible for loading the game and exposing the various interfaces.
GameState
To determine which state the game is in; whether it's loading, idling, playing or game over.
Player
Fetching the player's position and calculating the distance from the player's current position to a target position.
Inbox
Collecting all individual mails, and offering different filters.
Mail
Representing each single individual mail, with the ability to return the mail's current position and state.

Since InboxAttack supports two different methods of input; keyboard and mouse; I also created a Controller interface with two corresponding implementations. It's good practice to have the same interface for both input mechanisms so that you can keep them in sync. Since WebDriver supports both keyboard and mouse input through its Advanced User Interactions API, adding support for multiple controls was easy.

The robot performs much better than what I had imagined, with a perfect score on every go. The game itself could be expanded a bit by adding multiple levels with increased difficulty, but the prupose of this walkthrough was to show you a possible approach on how to automate tests for in-browser games.

Once you've finished your game's testing library (in whatever programming language; WebDriver probably supports your prefered language) you can start writing tests. InboxAttackRobot is an example of how to interact with and use the library.

Conclusion

This walkthrough and example gives you an idea how you can approach the problem of automating your tests for an in-browser JavaScript-supported game. Interactive front-end tests like these don't replace your unit tests or integration tests, but triggers the same code paths a user would, making them essential to the quality and coverage of your testing.

WebDriver is not limited to plain interaction with input fields and forms, and should arguably be a part of the tool set for the modern web.

I also think that some of the points I've made here really shows just how important web standards are for moving the web, as a platform, forward by providing tools and an ecosystem for making it trivial to not only write, but also test, applications and games on the web.