My previous post hinted that the Physical Object Model takes advantage of some underlying magic that makes different types of controls look similar. This "smudging" of controls into similarity comes courtesy of the controls abstraction layer.

While each different type of control is unique (else we wouldn't need so many of them) in the main they support a much smaller set of actions. Although you select a particular radio button but check or uncheck a text box but type text into a text box, you could also say that each case simply sets the control's value. (UIAutomation formalizes many of these patterns; our patterns do not exactly match up to UIAutomation's patterns however.) Thus users of the POM do not have to know the exact type of a control but rather simply that it can be invoked, or its value set, or whatever. This prevents many user interface changes from rippling out beyond the POM - while the specific makeup of a UI is likely to change multiple times, its semantics tend to be much more stable.

The controls abstraction layer also hides the method used to interact with a particular control. Consider a button: it can be clicked with the mouse, or the keyboard can be used to give it focus and then invoke it, or it can be virtually invoked via accessibility APIs. The controls abstraction layer wraps these details into control-specific providers that can be swapped in and out to change how a specific action is executed. Automatically varying execution method is of course exactly what Execution Behaviors are all about, so we Execution Behavior this swapping and take advantage of all the goodness that brings to the table.

Here's what all that theory looks like in code:

public class UICheckBox : UIControl<CheckBox>, IValueSettable<bool>
{
    internal UICheckBox(UIObject knownUIObject) : base(new Avalon.CheckBox(knownUIObject)) { }

    public void SetValue(bool value)
    {
        Providers.ChooseNewProvider();
        if (value)
        {
            this.Check();
        }
        else
        {
            this.Uncheck();
        }
    }

    public bool IsChecked { get { return (this.CheckState != ToggleState.Off); } }

    public ToggleState CheckState
    {
        get { return Providers.CheckBoxProvider.CheckState; }
        set 
        { 
            Providers.ChooseNewProvider();
            Providers.CheckBoxProvider.CheckState = value;
            DelayUtility.WaitForIdle();
        }
    }

    public void Check()
    {
        Providers.ChooseNewProvider();
        Providers.CheckBoxProvider.Check();
        DelayUtility.WaitForIdle();
    }

    public void Uncheck()
    {
        Providers.ChooseNewProvider();
        Providers.CheckBoxProvider.Uncheck();
        DelayUtility.WaitForIdle();
    }

    public void Toggle()
    {
        Providers.ChooseNewProvider();
        Providers.CheckBoxProvider.Toggle();
        DelayUtility.WaitForIdle();
    }
}

A check box supports the generic Value Settable pattern, so UICheckBox implements SetValue. It can occasionally be useful to interact with a control outside the bounds of the generic patterns, so UICheckBox provides check box-specific methods like Check and Uncheck as well. The implementation of each these methods simply delegates most of the work to whatever Check Box Provider the global Providers registry happens to have on hand at the time.

The providers are Execution Behvaiored by calling Providers.ChooseNewProvider at the start of every method. ChooseNewProvider is actually a Composite Execution Behavior that chooses and activates one of the sets of providers (i.e., mouse, keyboard, shortcut keys, accessibility APIs). Thus each successive POM call may execute using any of these options.

The controls abstraction layer is the last piece of the core automation stack. By combining it with the Physical Object Model and Logical Functional Model, and integrating Execution Behvaiors throughout, scripted test cases - which typically only ever do exactly the same thing - can be more than just regression tests. Rather than explicitly stating exactly what to do and how to do it they can instead give general directions. Each time a test case runs it is likely to take a different path than it has previously - and so it is likely to find new bugs. This lets scripted test cases start earning their keep!


*** Want a fun job on a great team? I need a tester! Interested? Let's talk: michhu at microsoft dot com. Great coding skills required.