• This is yet another way of writing selenium test, which targets making 80% of the test codes more expressive, succinct and noise-free.

    Problems of the normal selenium tests

    • type(), select()..., these selenium commands are in detail and wordy for complex form, test is hard to follow.  
    • test data tends to be weaved into selenium code, hard to be reused in a clear way.  
    • Thread.Sleep() and WaitForCondition() as a work around for timing issue mess up the codes.  
    • IDs or element locators are duplicated from the client side and spreaded everywhere in selenium code.
    • hard to catch javascript exceptions of the client side from selenium code.

     

    In a word, pure testing logics tend to be messed up with other noisy codes for different reasons.

    How it works

    jQuery.parcel is a young jQuery extension designed for better javascript encapsulation. One of the key features is getting/setting state of a part of web page through JSON object. When setting state, that part of page is populated with proper events fired just like real user interaction. This feature enables the new way of writing tests.

    In selenium code side, model class is created to represent a group of fields in page and the instance of model class is serialized to JSON, then send to selenium RC for page populating through selenium RunScript command. page state goes back to selenium code in a similar way.

    A 'Hello World' sample

    State getting/setting through jQuery.parcel

    Given a HTML snippet:

    <div id="person">
      <input name="name" type="text" />
      <select name="age">
        <option>please select</option>
        <option>18</option>
      </select>
      ...
    </div>
    

    Then

    $("#person").state();

    Returns {name: "", age: "please select", ...} as the state of the person div.

    $("#person").state({name: "luning", age: "30"});

    Input fields in person div are populated in proper order and with proper events(click, change, blur,...) fired. The name of the property in state matches the name of the input.

    state() can be called on any jQuery object including div, fieldset, input and so on.

    Model class in selenium code

    (C# for the following sample codes)

    public class PersonModel
    {
        public string name;
        public string age;
        ...
    }


    Choose a JSON serializer, compose javascript and run it through selenium

    Use any JSON serializer(JSON.Net, JavaScriptSerializer) you like to do the two way serialization.

    PersonModel model = new PersonModel{
                            name = "luning",
                            age = "30"
                        };
    string json = new JavaScriptSerializer().Serialize(model);

    Then, json string is sent to browser for page population.

    void Populate(string containerSelector, object model)
    {
        string json = new JavaScriptSerializer().Serialize(model);
        selenium.RunScript(string.Format("$('{0}').state({1});", containerSelector, json));
    }
    
    Populate("#person", new PageMode{
                            name = "luning",
                            age = "30"
                        });
    

    Code to get state from page

    T GetModel(string containerSelector)
    {
        var json = selenium.GetEval(string.Format("$('{0}').state();", containerSelector));
        return new JavaScriptSerializer().Deserialize<T>(json);
    }
    
    PersonModel model = GetModel<PersonModel>("#person");

    By now, we see the basic idea of how to write selenium tests with jQuery.parcel.

    More than 'Hello World'

    Above is a very simple 'Hello World'. In order to write full-fledge tests for complex pages, we need to leverage other features of seting state and create some javascript helper functions for testability. Here are some topics about this.

    More control on setting state

    Options are available as the second parameter of state(), which are useful for testing purpose, they are:

    • sync, true will turn off jQuery animation and run jQuery ajax in sync. This is useful if page has many async behaviours, and Thread.Sleep() and WaitForCondition() are no longer needed in this case.  
    • editable, true will check that the target field is visible and enabled before setting state.  
    • verify, true will check that the value is actually set as expected after setting state.  
    • exist, true will check that all properties in JSON object have corresponding fields in page.

     

    The new version setting state becomes:

    $("#person").state({name: "luning"}, {sync: true, editable: true, exist: true, verify: true});

    If any of the conditions is not satisfied, exception will be thrown in client side and eventually captured in selenium code. This is quite helpful for debuging.

    get/set state is not enough for all purposes

    Approximately speaking, 80% of the testing logic is about populating some fields, clicking something and verifying some other fields. 15% can be done by verifying the attributes(not just value) of a field. For the 5% left, we do them as the way we did them before.

    Some handy methods are needed in javascript which are used solely for testability. Here are two of them:

    • A javascript function returning attributes of a field as JSON is probalily the most useful one. Attributes may include common attributes of a DOM element like class, visibility and enabled, and also some attributes which are only meaningful for some particular type of input(eg. all options of select). Build corresponding class in selenium code and deserialize the JSON to it.  
    • A javascript function returning all errors of the page is another useful one for testability.

     

    The final test looks like:

    With the facilities above, the final test may look like below.

    ...
    page.Populate(new PersonModel{
                      name = "luning",
                      age = "please select",
                      // ...
                  });
    page.Confirm(); // click some button
    var errors = page.GetErrors(); // call javascript function to get errors on page
    Assert.AreEqual("age should be selected", errors);
    ...
    page.Populate(new PersonModel{
                      fieldA = "A",
                      fieldB = "B",
                      // ...
                  });
    var model = page.GetModel();
    Assert.AreEqual("some value", model.anotherField);
    ...


    Advantages compared to normal way of writing selenium tests

    get/set state rather than detail steps

    New way comes with a high level concept of state/model of page, and is able to populate fields or get state of fields in batch. This makes code more expressive and succinct expecially for complex form.

    Minimize duplication of ID and locator

    Test depends on field name, and it will notify us if name in model doesn't sync with name in DOM, because exception is thrown if name doesn't match any DOM element.

    Minimize the housekeeping codes

    Thanks to option sync of setting state, which makes test free from noisy housekeeping codes like Thread.Sleep() and WaitForCondition().

    Quicker

    Populate with state results in less round trips between selenium code and browser.

    Don't let off any javascript exception

    Javascript exceptions are catchable from selenium code while populating page, like exceptions in case of error in event handler or ajax callback, field name typo in model, setting state against invisible or disabled field or failing to set state to expected value. These help developer locate problem quickly.

    Easy to manage reusable test data

    Test data can be expressed as a model instance or a JSON string, and is easy to be extended or inherited. It can also be stored in txt file and deserialized into code easily. This is expecially useful for reusing test data of complex form.

  • Mark Needham just posted a blog here described the 'waiting for jQuery ajax call' problem in selenium tests and the way we solved it.

    The basic idea is recording the count of active ajax requests in client side js code, and generically decorating the calls(click, type, select etc.) in selenium tests with the logic of waiting for the count to be zero.

    Ajax request may not be the only async point we want to wait for its completion in selenium tests, animation is another one we care about, but just turn off it in tests so that we don't need to worry about it any more. "jQuery.fx.off = true" will turn off jQuery animation.

    Is there any more async points? probably YES. Acturally, what we really want to wait is the completion of any action which is delayed to be executed by window.setTimeout() or window.setInterval(). Ajax and animation rely on window.setTimeout() or window.setInterval() to simulate the async behaviours eventually.

    Please check out the code at mark's post or here(with comments) for details. It works for the cases you fire multiple ajax requests at once or fire further ajax request in previous ajax request's callback.

    Given this, we removed almost all noisy thread.sleep() and waitForCondition() codes in selenium tests! The build is speeded up and the tests become more concise, readable and stable.

    This approach is simple, stable, side-effect free and able to be ported to other ajax framework easily.(jQuery Ajax provides proper events we can rely on, for other ajax framework without build-in events, we can try method delegation instead)

    Waiting for ajax call is a common issue in selenium tests, try this approach and make your selenium tests cleaner.

  • 这是一个基于JSF的WEB应用,被测的场景是编辑一个巨大的表单,然后进行预览操作,最后再回到编辑页面,验证各个字段是否和以前的一样,据此间接检验各个页面元素与后台Bean的绑定是否正确。

    以前的测试不是人读的

    这是之前实现这一测试的代码片段:

    @Test
    public void createFullRequest_priview_backEditing_checkData(){
    goToCreationPage();
    browser.click(ADD_APPOINTMENT);
    browser.click("lorryTrucking1");
    browser.select("containerSizeType1_1", "label=20' Platform");
    typeAndBlur("containerQuantity1_1", "1");
    browser.select("cargoPackageType1_1", "label=Bar");

    ......