JGriffin's Blog

Is this thing on?

B2G and WebAPI testing in Emulators

Malini Das and I have been working on a new test framework called Marionette, in which tests of a Gecko-based product (B2G, Fennec, etc.) are driven remotely, ala Selenium.  Marionette has client and server components; the server side is embedded inside Gecko, and the client side runs on a (possibly remote) host PC.  The two components communicate using a JSON protocol over a TCP socket.  The Marionette JSON protocol is based loosely on the Selenium JSON Wire Protocol; it defines a set of commands that the Marionette server inside Gecko knows how to execute.

This differs from past approaches to remote automation in that we don’t need any extra software (i.e., a SUTAgent) running on the device, we don’t need special access to the device via something like adb (although we do use adb to manage emulators), nor do tests need to be particularly browser-centric.  These differences seem advantageous when thinking about testing B2G.

The first use case to which we might apply Marionette in B2G seems to be WebAPI testing in emulators.  There are some WebAPI features that we can’t test well in an automated manner using either desktop builds or real devices, such as WebSMS.  But we can write automated tests for these using emulators, since we can manipulate the emulator’s hardware state and emulators know how to “talk” to each other for the purposes of SMS and telephony.

Since Marionette tests are driven from the client side, they’re written in Python.  This is what a WebSMS test in Marionette might look like:

from marionette import Marionette

if __name__ == '__main__':
    # launch the emulators that will do the sending and receiving
    sender = Marionette(emulator=True)
    assert(sender.emulator.is_running)
    assert(sender.start_session())

    receiver = Marionette(emulator=True)
    assert(receiver.emulator.is_running)
    assert(receiver.start_session())

    # setup the SMS event listener on the receiver
    receiver.execute_script("""
        var sms_body = "";
        window.addEventListener("smsreceived",
                                 function(m) { sms_body = m.body });

    """)

    # send the SMS event on the sender
    message = "hello world!"
    sender.execute_script("navigator.sms.send(%d, '%s');" %
        (receiver.emulator.port, message))

    # verify the message was received by the receiver
    assert(receiver.execute_script("return sms_body;") == message)

The JavaScript portions of the test could be split into a separate file from the Python, for easier editing and syntax highlighting.  Here’s the adjusted Python file:

from marionette import Marionette

if __name__ == '__main__':
    # launch the emulators that will do the sending and receiving and
    # load the JS scripts for each
    sender = Marionette(emulator=True)
    assert(sender.emulator.is_running)
    assert(sender.start_session())
    assert(sender.load_script('test_sms.js'))

    receiver = Marionette(emulator=True)
    assert(receiver.emulator.is_running)
    assert(receiver.start_session())
    assert(receiver.load_script('test_sms.js'))

    # setup the SMS event listener on the receiver
    receiver.execute_script_function("setup_sms_listener")

    # send the SMS event on the sender
    message = "hello world!"
    target = receiver.emulator.port
    sender.execute_script_function("send_sms", [target, message])

    # verify the message was received by the receiver
    assert(receiver.execute_script_function("get_sms_body") == message)

And here’s the JavaScript file:

function send_sms(target, msg) {
    navigator.sms.send(target, msg);
}

var sms_body = "";

function setup_sms_listener() {
    window.addEventListener("smsreceived",
                            function(m) { sms_body = m.body });
}

function get_sms_body() {
    return sms_body;
}

Both of these options are just about usable in Marionette right now.  Note that the test is driven, and some of the test logic (like asserts) resides on the client side, in Python.  This makes synchronization between multiple emulators straightforward, and provides a natural fit for Python libraries that will be used to interact with the emulator’s battery and other hardware.

What if we wanted JavaScript-only WebAPI tests in emulators, without any Python?  Driving a multiple-emulator test from JavaScript running in Gecko introduces some complications, chief among them the necessity of sharing state between the tests, the emulators, and the Python testrunner, all from within the context of the JavaScript test.  We can imagine such a test might look like this:


var message = "hello world!";
var device_number = Marionette.get_device_number(Marionette.THIS_DEVICE);

if (device_number == 1) {
  // we're being run in the "sender"

  // wait for the test in the other emulator to be in a ready state
  Marionette.wait_for_state(Marionette.THAT_DEVICE, Marionette.STATE_READY);

  // send the SMS
  navigator.sms.send(Marionette.get_device_port(Marionette.THAT_DEVICE), message);
}
else {
  // we're being run in the "receiver"

  // notify Marionette that this test is asynchronous
  Marionette.test_pending();

  // setup the event listener
  window.addEventListener("smsreceived",
                          function (m) { 
                                         // perform the test assertion and notify Marionette 
                                         // that the test is finished
                                         is(m.body, message, "Wrong message body received"); 
                                         Marionette.test_finished();
                                       }
                         );

  // notify Marionette we're in a ready state
  Marionette.set_state(Marionette.STATE_READY);
}

Conceptually, this is more similar to xpcshell tests, but implementing support for this kind of test in Marionette (or inside the existing xpcshell harness) would require substantial additional work. As it currently exists, Marionette is designed with a client-server architecture, in which information flows from the client (the Python part) to the server (inside Gecko) using TCP requests, and then back. Implementing the above JS-only test syntax would require us to implement the approximate reverse, in which requests could be initiated at will from within the JS part of the test, and this would require non-trivial changes to Marionette in several different areas, as well as requiring new code to handle the threading and synchronization that would be required.

Do you think the Python/JS hybrid tests will be sufficient for WebAPI testing in emulators?

5 responses to “B2G and WebAPI testing in Emulators

  1. Andrew Sutherland November 14, 2011 at 3:56 pm

    This all sounds great! The choice of python for the driver right now makes a lot of sense given the rest of Mozilla’s test driving infrastructure and most of the server infrastructure is written in python. And since things are decoupled via the JSON TCP protocol, one could migrate to driving things to JS using node or the like if that makes sense. Hopefully there is not too much drift in the protocol so things that work with Selenium could be easily adapted to work with your protocol.

    For the deuxdrop messaging experiment (https://github.com/mozilla/deuxdrop) I am currently hooking up Selenium to our existing node.js-based unit test framework, so the Marionette route is very attractive to me as it provides a migration path for testing on mobile as XUL-based Fennec is retired. (The test framework uses node because the server logic is all in JS so we can share code between the client and the server.)

    Right now we’re about to test with desktop firefox so we can use the Selenium firefox driver and will just assume that XUL Fennec works the same.

    If you do start to do any JS API work, please check out the fairly new webdriver-jsapi work happening in the selenium repo; it seems like the best node-compatible implementation I’ve seen:
    http://code.google.com/p/selenium/source/browse/trunk/javascript/webdriver-jsapi/

    • jagriffin November 14, 2011 at 6:15 pm

      In fact I’ve written a Selenium proxy that allows Selenium tests to work seamlessly via Marionette; it translates the Selenium JSON Wire Protocol into Marionette’s own protocol. We haven’t implemented support for all the commands in the Selenium protocol yet, but that’s the eventual goal. This way you’ll be able to run Selenium tests (written in any language Selenium has a library for) via Marionette in products like B2G, in which it might be otherwise difficult due to problems loading the Firefox driver extension.

  2. jhammink December 31, 2011 at 1:30 am

    I’m one of the people who will be using this – great work to you and Malini! – and I’m looking forward to it. I finally got B2G built and flashed on my device. Is it possible (by now, December 31) to use Marionette to begin automating web api unit tests on my Galaxy S2 running B2g?

    Incidentally, to elaborate your example, it’s possible to make one single file (assuming the device has a working sim card) that sends sms message to itself and then listens for that message. My example of that can be derived from this:

    https://github.com/jhammink/WebAPI-test-pages/blob/master/SMSv5.html

    Again, I’m very excited to be working with this soon!
    .john

  3. Mattwallis31@gmail.com May 28, 2012 at 1:26 am

    Hry you all

Leave a comment