Saturday, January 28, 2012

CodedUI: Close Dialogs and Windows

UI automation could be a very very complex procedure. Several things could change between different executions, controls could be moved or renamed, etc.

Some simple things such as closing a dialog popup/window could be challenging..

Let’s try to recreate the problem. File/New Project in Visual Studio 2010, then select Windows Forms.

Add a button with the following handler on the click event:

MessageBox
private void button1_Click(object sender, EventArgs e)
{
    MessageBox.Show("Try closing me..", "Try closing me..", MessageBoxButtons.OKCancel);
}

Record a CodedUI test that opens our super simple WinForm App, click Button1 and then close the message box. Finally, add one last step to record that closes the Main Window.

Now run the previous CodeUI test and check how the test reports a successful execution. Note that your Message Box is still opened and your application is still waiting for input.

* I imagine that it should be a better way of working around this effect than the following. If you figure it out let me know :).

Let’s create a UIMapExtensions class (containing extensions methods of course)

UIMapExtensions
public static class UIMapExtensions
{
    public delegate void CloseTestControlCallback();

    public static bool CloseWindow(this UITestControl testControl, CloseTestControlCallback closeCallback)
    {
        var closeSuccess =
            testControl.WaitForControlCondition(
                tc =>
                {
                    closeCallback();
                    return !tc.Exists;
                }, 3000);
        return closeSuccess;
    }

    public static bool CancelDialog(this UITestControl testControl)
    {
        var closeSuccess =
            testControl.WaitForControlCondition(
                tc =>
                {
                    Keyboard.SendKeys("{ESC}");
                    return !tc.Exists;
                }, 3000);
        return closeSuccess;
    }

    public static bool AcceptDialog(this UITestControl testControl)
    {
        var closeSuccess =
            testControl.WaitForControlCondition(
                tc =>
                {
                    Keyboard.SendKeys("{ENTER}");
                    return !tc.Exists;
                }, 3000);
        return closeSuccess;
    }

    public static bool CloseWindow(this UITestControl testControl)
    {
        var closeSuccess =
            testControl.WaitForControlCondition(
                tc =>
                {
                    WinApi.PostMessage(tc.WindowHandle, WinApi.WM_CLOSE, 0, 0);
                    return !tc.Exists;
                }, 3000);
        return closeSuccess;
    }
}

I am using 3 techniques in the previous code fragment. In each one of them, we wait for the control to respond to the close action (for 3000 ms).

  1. CloseWindow that receives a delegate to the close action. You can pass in the original Close method generated by the UIMap as you will see in the Test Method code below.
  2. CloseWindow with no arguments using PostMessage Win API and P/Invoke (this is some how brute force :)).
  3. AcceptDialog and CancelDialog: assuming it is a message box with Ok/Cancel buttons and that the window is in the foreground.

Now you can just replace some of generated test code in the Test Class (not in the UIMap):

Test Method
[TestMethod]
public void CodedUITestMethod1()
{
    this.UIMap.OpenApplication();
    this.UIMap.OpenPopup();
    //this.UIMap.ClosePopup();

    // try one of the following..
    var closeSuccess = this.UIMap.UItryclosingmeWindow.CloseWindow(this.UIMap.ClosePopup);
    //var closeSuccess = this.UIMap.UItryclosingmeWindow.AcceptDialog();
    //var closeSuccess = this.UIMap.UItryclosingmeWindow.CancelDialog();
    //var closeSuccess = this.UIMap.UItryclosingmeWindow.CloseWindow();
    if (!closeSuccess)
        Assert.Fail("Popup not closed!");

    this.UIMap.CloseApplication();
}

My two cents..