A Peek Behind The Curtains

With the LFM defined and hooked up to Execution Behaviors it makes sense to move on to defining the Physical Object Model.

The POM is often the simplest part because it's just a straightforward API around the user interface. Given Surveyor's UI:

A sketch of Surveyor's UI

a likely POM would be:

public class UIApplicationWindow : UIWindow
{
internal UIApplicationWindow(UIObject applicationWindow)
: base(new Window(applicationWindow)) { }

    public UIQuestionSetButton SwitchToNotYetAnsweredQuestions { get { } }
public UIQuestionSetButton SwitchToSkippedQuestions { get { } }
public UIQuestionSetButton SwitchToAnsweredQuestions { get { } }

    public UIQuestion CurrentQuestion { get { } }

    public UIButton MoveToPreviousQuestion { get { } }
public UIButton MoveToNextQuestion { get { } }
public UIButton FinishSurvey { get { } }
}
Although the POM is a very thin wrapper around the UI, omitting UI elements used only for layout can vastly simplify the POM. For example, Surveyor's UI uses stack panels and assorted other layout mechanisms to format its user interface, but none of these are important for execution or verification and so the POM does not mention them.

Much of the POM uses predefined classes that provide standard behavior (such as UIWindow, which has Close, Move, and Resize methods), but many applications require some custom controls as well. Custom controls that merely add functionality to a standard control are straightforward to handle: simply derive from that standard control and add the necessary behaviors. Surveyor's question set navigation buttons (the ones that allow the user to switch between those questions they haven't yet answered, those they've skipped, and those they've answered) are nothing more than styled radio buttons, so UIQuestionSetButton class could be defined like so:
public class UIQuestionSetButton : UIRadioButton
{
internal UIQuestionSetButton(UIObject theQuestionSetButton)
: base(theQuestionSetButton) { }

    public int QuestionCount { get { } }
}
where UIRadioButton provides the expected ability to select itself and to report whether it is selected.

Other cases that involve a truly custom control are handled by deriving from (wait for it...) UICustomControl. An example of this in Surveyor is the current question:
public class UIQuestion : UICustomControl
{
internal UIQuestion(UIObject theQuestion)
: base(theQuestion) { }

    public string Question { get { } }

    public UIRadioButton GetAnswer(string name) { get { } }

    public UIButton ClearAnswer { get { } }
public UIButton SkipQuestion { get { } }
}
Although the UI does not clearly separate it as such, a question has multiple parts: the text of the question, the set of possible answers, the button to clear the selected answer, and the button to skip the question. Grouping those related concepts into a single object helps make the POM easier to work with and understand (for me, at least).

In the main, implementing the individual properties is trivial. UIQuestion.Question's getter is just:
public string Question
{
get
{
UICustomControl value = this.FindDescendant<UICustomControl>("Question");
return value.Name;
}
}
Its ClearAnswer getter is even simpler:
public UIButton ClearAnswer
{
get
{
return this.FindDescendant<UIRadioButton>("ClearAnswer");
}
}
FindDescendant wraps the tree walk services that Avalon and UIAutomation provide. Generics (new in version two of the .Net Framework) takes care of casting the generic object those services return to the specific type of control we're looking for. (And yes, I normally pull those inlined constants out to named constants!)

To use the POM, simply dot your way down the tree:
// Logical.Application.MainWindow returns a UIApplicationWindow.
UIRadioButton answer = Logical.Application.MainWindow.CurrentQuestion.GetAnswer(answerId);
answer.Invoke();
Logical.VerificationLog.VerifyTrue(answer.IsSelected, "Answer " + answer.Label + " is selected");

As I said, straightforward stuff!

 

*** 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.