Be carefull with PushChildButton

by Piotr Kałuski
www.piotrkaluski.com
pkaluski@piotrkaluski.com

PushChildButton, in Win32::GuiTest perl package, is a function, which allows to simulate pushing a button. You specify the handle of the parent window (the window, which has the button you want to push) and then you specify button's control id or button's text. As you can see, it's really convenient. However, as a side effect of this convenience, PushChildButton may behave unexpectatly in some circumstances, and in the same time behave correctly in almost identical ones. Let me give an example. It is slightly complex, but I do it for purpose, to help you realize how substle could be the reason of problem.:

Example

Imagine you are testing an application, which creates new bank accounts. You have a GUI form, where you type customer's data and bank account. Once you are done you click "Next". Then you get a next window for entering some more data. This window has a static label with the account number (which was inserted in the previous window). Apart from that, it has a bunch of other controls and a button "Next" with control id equal to 5436.
You write automated tests for it. The part of the code which pushes the second "Next" button, will probably look like this:

.... code .....
PushChildButton( $parrent, 5436 );
.... code ....

You plan to run the test for some accounts. You run it for account number 21-3678463-2814673264. It worked. Then 43-3428934-3489234. It worked. Then 78-345436589-37843578. Oppss... It did not work. Everythink goes well, but in the second window the button "Next" is not pushed, although PushChildButton does not return any error. What has happened?

In order to better understand what was going on, we have to look at the code:

sub PushChildButton {
    my $parent = shift;
    my $button = shift;
    my $delay  = shift;
    $delay = 0 unless defined($delay);
    for my $child (GetChildWindows($parent)) {
        # Is correct text or correct window ID?
        if (MatchTitleOrId($child, $button)) {
            # Need to use PostMessage.  SendMessage won't return when certain dialogs come up.
            PostMessage($child, WM_LBUTTONDOWN(), 0, 0);
            # Allow for user to see that button is being pressed by waiting some ms.
            select(undef, undef, undef, $delay) if $delay;
            PostMessage($child, WM_LBUTTONUP(), 0, 0);
            return;
        }
    }
}

Look at bolded italics part. The function get's all children (direct children, grand-children and so on). Then for each child it calls MatchTitleOrId to check if child's id or caption matches PushChildButton "$button" parameter. Let's have a look a MatchTitleOrId:

sub MatchTitleOrId {
    my $wnd = shift;
    my $regex = shift;
    my $title = GetWindowText($wnd);
    my $id = GetWindowID($wnd);
    return $title =~ /$regex/i ||
        $regex =~ /^\d+$/ && $regex == $id;
}

It takes a control id of a child and child's caption and checks if any of this matches the $button parameter from PushChildButton.
OK. So why it worked for some accounts but did not work for account 78-345436589-37843578. Any idea? First hint. Remember, the control id of a button, which was causing problems was 5436. Still no clue? OK. Look at the account number once again: 78-345436589-37843578. See? It contains a group of digits which are exactly the same like button's Id. So what happens, when PushChildButton is called? It will iterate through all children of the second window. A static label with with the account number is one of them. If this label happens to appear in the list of children before "Next" button, MatchTitleOrId will look for button's id string in the static label's text and will find it. And PushChildButton will try to push the static label with the account number.

How to fix this

It's difficult to talk about the fix, since the behaviour of PushChildButton is not a bug. It is a side effect of convenience and generality it offers. We can talk about workaround. For me, the best solution would be to add new function, which will look for a button only amongst its direct children. The good solution is to enclose the button id by ^ and $ ("^5436$") so it will look only for strings, which are exactly the same, not almost the same. This will significantly reduce the probability of mistake, however will not eliminate it totaly. Anyway, whatever the solution is, be carefull with PushChildButton.