Automate interaction with Windows dialogs/windows using AutoIt - not needed SOAtest 9.0 and higher
Web applications primarily use the JavaScript function window.open to create a new window. SOAtest handles these windows as expected.
Internet Explorer -- and more recently, Firefox -- provide two additional functions for creating windows, window.showModalDialog and window.showModelessDialog. For technical reasons, SOAtest (current version 6.2) can neither record nor playback actions on a window created through these two methods.
You can handle modal/modeless dialogs during playback of web functional tests by using an AutoIt script. SOAtest can run the AutoIt script that then waits for and interacts with the modal/modeless dialog. In AutoIt, you can simulate the clicks necessary to interact with the dialog.
AutoIt allows you to create scripts to automate Windows tasks. From the AutoIt website:
You can download AutoIt here: http://www.autoitscript.com/It [AutoIt] uses a combination of simulated keystrokes, mouse movement and window/control manipulation in order to automate tasks in a way not possible or reliable with other languages (e.g. VBScript and SendKeys). AutoIt is also very small, self-contained and will run on all versions of Windows out-of-the-box with no annoying "runtimes" required!
To utilize AutoIt in SOAtest, do the following:
- Create and compile an AutoIt script. You now have the file yourScriptName.exe.
- Test the script while manually testing the application.
- Once you have determined that the script does what you want, add an External Tool in SOAtest to run the script. The External Tool can run any program. You must add the External Tool before the browser test that creates the user dialog, so that the AutoIt script is waiting in the background for the dialog to appear. For example, the following scenario:
- Test 1: Some browser test
- Test 2: External Tool to start AutoIt script.
- Test 3: Browser test with user action that creates the modal/modeless dialog.
- Open the External Tool and configure the following options:
- Executable: Path to compiled .exe you created with AutoIt.
- Flags/Arguments: Whatever command line parameters are necessary to run the script. In the case of handling modal dialogs, typically these would provide a means to identify the dialog to use and the button to click.
- Wait for executable to finish: uncheck this option, so that the script runs in the background.
To get you started, consider the following script. It uses command line arguments to find the dialog and button to use, making the script general enough to handle many dialogs.
; For methods that start with _IE.
#include <IE.au3>
; To create an exe that outputs to the console, compile with Aut2Exe
; with the "Console" option selected.
; Clicks on a specified button within an IE modal dialog -- that is,
; a dialog created with window.showModalDialog.
; Also works with IE modeless dialogs -- that is, a dialog created
; with window.showModelessDialog. Modal is more common, hence the
; name of the script.
; Identify the modal dialog using a substring of the window title.
; Identify the button to click on within the dialog by specifying
; either the id or value attribute value.
; An example button:
; <input type="button" id="closeButton" value="close"/>
; You can locate the button using either the "closeButton" id
; attribute value or the "close" value attribute value.
; See ParseArgs for the expected command line parameters.
; Require variable declaration to increase script maintainability.
Opt("MustDeclareVars", 1);0=no, 1=require pre-declare
; Break all functionality into functions to avoid global variables.
Main()
Func Main()
Local $windowName = ""
Local $elementId = ""
Local $buttonValue = ""
Local $timeout = 60
Local $noForceClose = False
; Make a command line argument if useful.
Const $sleepTime = 5000
ParseArgs($windowName, $elementId, $buttonValue, $timeout, $noForceClose)
$buttonValue = StringStripWS($buttonValue, 3)
ConsoleWrite("Waiting for window with title = " & $windowName & @CRLF)
Local $windowFound = WinWait($windowName, "", $timeout)
If $windowFound Then
ConsoleWrite("Found window." & @CRLF)
Else
ConsoleWrite("Could not find window: " & $windowName & @CRLF)
Exit(1)
EndIf
; Assume that this will always succeed because WinWait succeeded.
; Use window handle to avoid searching by window name each time,
; so as to ensure that we are consistenly working with the same
; window in the unlikely event that another window with the same
; name may appear.
Local $dialogHandle = WinGetHandle($windowName, "")
; Give us some time to see the dialog for simple debugging.
Sleep($sleepTime)
; Get a handle to the IE instance. Use "DialogBox" search mode
; because it is a modal (or modeless) dialog that we are searching for.
Local $ie = _IEAttach($dialogHandle, "DialogBox")
If $ie == 0 Then
ConsoleWrite("Window is not instance of IE: " & $windowName & @CRLF)
Exit(1)
EndIf
; Find the button we want and click it.
Local $button = 0
If $button == 0 Then
$button = FindButtonByValue($ie, $buttonValue)
EndIf
If $button == 0 Then
$button = FindButtonById($ie, $elementId)
EndIf
If $button == 0 Then
ConsoleWrite("Could not find element using any parameters" & @CRLF)
Else
ConsoleWrite("Performing click action." & @CRLF)
_IEaction($button, "click")
EndIf
; Give window time to close.
Sleep($sleepTime)
; If the window still exists then:
; We didn't find anything to click or...
; Our clicking didn't close the dialog. Abort! We don't want the dialog
; hanging around preventing further interaction with the other IE
; windows (assuming the dialog is modal).
If WinExists($dialogHandle) Then
If $noForceClose Then
ConsoleWrite("Window is still open." & @CRLF)
Else
WinClose($dialogHandle)
ConsoleWrite("Forced window to close." & @CRLF)
EndIf
Exit(1)
Else
ConsoleWrite("Closed window via click." & @CRLF)
EndIf
EndFunc
Func ParseArgs(ByRef $windowName, ByRef $elementId, ByRef $buttonValue, _
ByRef $timeout, ByRef $noForceClose)
Local $argc = $CmdLine[0]
For $i = 1 to $argc
Local $arg = $CmdLine[$i]
Switch $arg
Case "--window"
$i += 1
$windowName = $CmdLine[$i]
; id attribute value of button to click on in dialog.
Case "--element-id"
$i += 1
$elementId = $CmdLine[$i]
; value attribute value of button to click on in dialog.
Case "--button-value"
$i += 1
$buttonValue = $CmdLine[$i]
Case "--timeout"
$i += 1
$timeout = Int($CmdLine[$i])
; Don't forcibly close the dialog if we found the dialog but not the
; button, or if clicking on the button did not close the dialog.
Case "--no-force-close"
$noForceClose = True
Case Else
ConsoleWrite("Unknown option " & $arg & @CRLF)
EndSwitch
Next
EndFunc
Func FindButtonByValue($ie, $buttonValue)
Local $button = 0
If Not StringIsSpace($buttonValue) Then
Local $inputs = _IETagNameGetCollection($ie, "input")
For $element In $inputs
Local $value = StringStripWS($element.value, 3)
If $value == $buttonValue And $element.type == "button" Then
$button = $element
ConsoleWrite("Found element using button value = '" & _
$buttonValue & "'" & @CRLF)
ExitLoop
EndIf
Next
EndIf
If $button == 0 Then
ConsoleWrite("Could not find element using button value = '" & _
$buttonValue & "'" & @CRLF)
EndIf
Return $button
EndFunc
Func FindButtonById($ie, $elementId)
Local $button = 0
If Not StringIsSpace($elementId) Then
$button = _IEGetObjById($ie, $elementId)
EndIf
If $button <> 0 Then
ConsoleWrite("Found element using element id = '" & _
$elementId & "'" & @CRLF)
Else
ConsoleWrite("Could not find element using element id = '" & _
$elementId & "'" & @CRLF)
EndIf
Return $button
EndFunc
Comments
-
SOAtest 9.0 and higher handle modal and modeless dialogs natively: you no longer need to use AutoIt. That said, AutoIt remains a useful tool for handling windows and dialogs from the browser that are not HTML pages. For example, to handle a "Save As" dialog you would need to use AutoIt to get a handle to the dialog, type into it, and click the desired button. Browser test offer means to interact with HTML documents, not any arbitrary window created by the operating system.0
-
Windows print dialog: select printer, then print
Here is an example on how to interact with a print dialog in Windows. The script waits for a window with the name "Print", then chooses the desired printer. Modify the script to press the button to actually print the document: pressing the button to print is commented out to make it easy to see that it selects the proper printer (and otherwise debugging your script) without using reams of paper.
Two print types of print dialogs are common, one with a combo box (drop-down list) of available printers and an "OK" button to print; the other with a list view of printers and a "Print" button to print. This script attempts to handle both.
To determine the criteria to use to find a control in a given window, use the "AutoIt Window Info" application. This application provided the information to create control locators such as "[CLASS:SysListView32; Text:FolderView]" and "[CLASS:Button; Text:&Print]" used below.
You may be tempted to use simple commands to send keystrokes. However, note that sending keystrokes does not work as expected if the application you want to interact with is running without a display, such as if you run the application using a Hudson job. Finding the controls to interact with and using AutoIt functions to send commands to the controls will work whether or not the application is visible.CODE; Require variable declaration to increase script maintainability.
Opt("MustDeclareVars", 1);0=no, 1=require pre-declare
Main()
; Avoid global variables by implementing everything in functions
; and using the "Local" keyword. In this script it doesn't matter (there
; is only one function), but this is a good practice to maintain to make
; your scripts less mysterious.
Func Main()
Local Const $dialogTitle = "Print"
Local Const $timeout = 20
; Printer name should be a command line argument.
Local Const $printerName = "Microsoft XPS Document Writer"
Local $windowFound = WinWait($dialogTitle, "", $timeout)
; For some reason if the print dialog contains the list view (see below)
; and the window did not exist before starting WinWait, then the
; window returned seems to be something transient that we can't work
; with. Therefore, try to find the window again. Unclear why this
; would be necessary, but this seems peculiar to this type of print
; dialog.
$windowFound = WinWait($dialogTitle, "", $timeout)
Local $windowHandle
If $windowFound Then
; Get the last window used by an AutoIt function, namely the window
; we were waiting for.
$windowHandle = WinGetHandle("[LAST]")
Else
ConsoleWrite("Could not find window " & $dialogTitle & @CRLF)
Exit(1)
EndIf
Local $listViewHandle = ControlGetHandle($windowHandle, "", "[CLASS:SysListView32; Text:FolderView]")
If $listViewHandle <> "" Then
; ControlListView sometimes doesn't work if this script is compiled as 32-bit
; and the application that is printing is 64-bit, or vice versa.
; See the documentation for ControlListView.
ConsoleWrite("Using listview" & @CRLF)
Local $printerIndex = ControlListView($windowHandle, "", $listViewHandle, "FindItem", $printerName)
ConsoleWrite("index: " & $printerIndex & @CRLF)
If $printerIndex < 0 Then
ConsoleWrite("Could not find printer " & $printerName & " in print list view" & @CRLF)
Exit(1)
EndIf
ControlListView($windowHandle, "", $listViewHandle, "Select", $printerIndex)
; Enable the statement below to print.
; ControlClick($windowHandle, "", "[CLASS:Button; Text:&Print]")
Else
; We have an old-style print dialog.
; Unlike the print dialog with the list view used above, a
; 32-bit/64-bit combination of script and application (or vice versa)
; is not a problem.
ConsoleWrite("Using combo" & @CRLF)
ControlCommand($windowHandle, "", "[CLASS:ComboBox; INSTANCE:1]", "SelectString", $printerName)
; Enable the statement below to print.
; ControlClick($windowHandle, "", "[CLASS:Button; Text:OK]")
EndIf
; Enable the statement below to cancel printing.
; ControlClick($windowHandle, "", "[CLASS:Button; Text:Cancel]")
EndFunc0 -
Windows print dialog: select printer, then print
Here is an example on how to interact with a print dialog in Windows. The script waits for a window with the name "Print", then chooses the desired printer. Modify the script to press the button to actually print the document: pressing the button to print is commented out to make it easy to see that it selects the proper printer (and otherwise debugging your script) without using reams of paper.
Two print types of print dialogs are common, one with a combo box (drop-down list) of available printers and an "OK" button to print; the other with a list view of printers and a "Print" button to print. This script attempts to handle both.
To determine the criteria to use to find a control in a given window, use the "AutoIt Window Info" application. This application provided the information to create control locators such as "[CLASS:SysListView32; Text:FolderView]" and "[CLASS:Button; Text:&Print]" used below.
You may be tempted to use simple commands to send keystrokes. However, note that sending keystrokes does not work as expected if the application you want to interact with is running without a display, such as if you run the application using a Hudson job. Finding the controls to interact with and using AutoIt functions to send commands to the controls will work whether or not the application is visible.CODE; Require variable declaration to increase script maintainability.
Opt("MustDeclareVars", 1);0=no, 1=require pre-declare
Main()
; Avoid global variables by implementing everything in functions
; and using the "Local" keyword. In this script it doesn't matter (there
; is only one function), but this is a good practice to maintain to make
; your scripts less mysterious.
Func Main()
Local Const $dialogTitle = "Print"
Local Const $timeout = 20
; Printer name should be a command line argument.
Local Const $printerName = "Microsoft XPS Document Writer"
Local $windowFound = WinWait($dialogTitle, "", $timeout)
; For some reason if the print dialog contains the list view (see below)
; and the window did not exist before starting WinWait, then the
; window returned seems to be something transient that we can't work
; with. Therefore, try to find the window again. Unclear why this
; would be necessary, but this seems peculiar to this type of print
; dialog.
$windowFound = WinWait($dialogTitle, "", $timeout)
Local $windowHandle
If $windowFound Then
; Get the last window used by an AutoIt function, namely the window
; we were waiting for.
$windowHandle = WinGetHandle("[LAST]")
Else
ConsoleWrite("Could not find window " & $dialogTitle & @CRLF)
Exit(1)
EndIf
Local $listViewHandle = ControlGetHandle($windowHandle, "", "[CLASS:SysListView32; Text:FolderView]")
If $listViewHandle <> "" Then
; ControlListView sometimes doesn't work if this script is compiled as 32-bit
; and the application that is printing is 64-bit, or vice versa.
; See the documentation for ControlListView.
ConsoleWrite("Using listview" & @CRLF)
Local $printerIndex = ControlListView($windowHandle, "", $listViewHandle, "FindItem", $printerName)
ConsoleWrite("index: " & $printerIndex & @CRLF)
If $printerIndex < 0 Then
ConsoleWrite("Could not find printer " & $printerName & " in print list view" & @CRLF)
Exit(1)
EndIf
ControlListView($windowHandle, "", $listViewHandle, "Select", $printerIndex)
; Enable the statement below to print.
; ControlClick($windowHandle, "", "[CLASS:Button; Text:&Print]")
Else
; We have an old-style print dialog.
; Unlike the print dialog with the list view used above, a
; 32-bit/64-bit combination of script and application (or vice versa)
; is not a problem.
ConsoleWrite("Using combo" & @CRLF)
ControlCommand($windowHandle, "", "[CLASS:ComboBox; INSTANCE:1]", "SelectString", $printerName)
; Enable the statement below to print.
; ControlClick($windowHandle, "", "[CLASS:Button; Text:OK]")
EndIf
; Enable the statement below to cancel printing.
; ControlClick($windowHandle, "", "[CLASS:Button; Text:Cancel]")
EndFunc0 -
Here is a script to close the "The web page you are viewing is trying to close the window" dialog box:
Global $foundOne
Global $sleepVal
$sleepVal = 1000
While 1
process_window_if_it_exists("Windows Internet Explorer", "The webpage you are viewing is trying to close the window.","Button1") ;IE7
process_window_if_it_exists("Microsoft Internet Explorer", "The Web page you are viewing is trying to close the window.","Button1") ;IE6
process_window_if_it_exists("Warning: Unresponsive script", "A script on this page may be busy, or it may have stopped responding.", "Continue") ;FireFox 2
if $foundOne < 10 Then
$sleepVal = 100
$foundOne = $foundOne + 1
Else
$foundOne = 11
$sleepVal = 5000 ;5 seconds
EndIf
sleep($sleepVal)
wend
func process_window_if_it_exists($winTitle, $checkMsg, $buttonName)
if WinExists($winTitle,"") Then
if StringLeft(ControlGetText($winTitle,"","Static2"),StringLen($checkMsg)) = $checkMsg then
ControlClick($winTitle,"",$buttonName)
sleep(1000)
ControlClick($winTitle,"",$buttonName)
Exit(1)
endif
sleep(100)
$foundOne = 1
EndIf
endfunc0 -
Handle Internet Explorer file download dialog
For when clicking (or some other action) results in Internet Explorer prompting you whether you want to save a file and where. Note that for SOAtest to trigger the file save dialog in IE, you must enable "Automatic prompting for file downloads" in the IE settings. That is, select the following in IE: Tools > Internet Options > Security > Internet (or whatever zone your tested site is in) > Custom Level... > Downloads > Automatic prompting for file downloads > Enable.
As with other AutoIt scripts, compile the script into a .exe file and use an external tool to start the .exe before the user action that generates the file save dialog.CODEOpt("MustDeclareVars", 1);0=no, 1=require pre-declare
Const $defaultTimeoutSeconds = 20
Const $defaultSleepMs = 2000
Main()
Func Main()
Local $outputFileName = "c:\tmp\output.txt"
Local $downloadWaitSeconds = 60
Local $cancel = False
ParseArgs($outputFileName, $downloadWaitSeconds, $cancel)
Local $fileDownloadWindowHandle = GetWindowHandle("File Download")
If $cancel Then
ClickButtonInWindow($fileDownloadWindowHandle, "Cancel")
Else
ClickButtonInWindow($fileDownloadWindowHandle, "&Save")
CompleteSaveAsDialog($outputFileName)
HandleConfirmationDialog()
CloseDownloadCompleteDialog($downloadWaitSeconds)
EndIf
EndFunc
Func ClickButtonInWindow($windowHandle, $buttonText)
WinActivate($windowHandle)
; Unclear why sleeping is necessary, but if you perform the click too
; soon then the active control changes to the save button but it
; does not appear to be clicked.
Sleep($defaultSleepMs)
ControlClick($windowHandle, "", "[CLASS:Button; TEXT:" & $buttonText & "]")
EndFunc
Func CompleteSaveAsDialog( _
$outputFileName, _
$timeoutSeconds = $defaultTimeoutSeconds)
Local $windowHandle = GetWindowHandle("Save As", $timeoutSeconds)
WinActivate($windowHandle)
Sleep($defaultSleepMs)
ControlSetText($windowHandle, "", "[CLASS:Edit; INSTANCE:1]", $outputFileName)
ControlClick($windowHandle, "", "[CLASS:Button; TEXT:&Save]")
EndFunc
Func HandleConfirmationDialog($timeoutSeconds = 5)
; This won't appear if there there is not a preexisting file with
; the same name. Therefore, it is not an error if the wait times out.
Local $windowHandle = WinWait("Confirm Save As", "", $timeoutSeconds)
If $windowHandle <> 0 Then
WinActivate($windowHandle)
Sleep($defaultSleepMs)
ControlClick($windowHandle, "", "[CLASS:Button; TEXT:&Yes]")
EndIf
EndFunc
Func CloseDownloadCompleteDialog($downloadWaitSeconds)
; Whether the dialog appears upon the download completing is dependent
; on your settings. Therefore, it is not an error if the wait times out.
Local $windowHandle = WinWait("Download complete", "", $downloadWaitSeconds)
If $windowHandle <> 0 Then
WinClose($windowHandle)
EndIf
EndFunc
Func GetWindowHandle($windowTitle, $timeoutSeconds = $defaultTimeoutSeconds)
Local $windowHandle = WinWait($windowTitle, "", $timeoutSeconds)
If $windowHandle == 0 Then
ConsoleWrite("Could not find window " & $windowTitle & @CRLF)
Exit(1)
EndIf
Return $windowHandle
EndFunc
Func ParseArgs( _
ByRef $outputFileName, _
ByRef $downloadWaitSeconds, _
ByRef $cancel)
Local $argc = $CmdLine[0]
For $i = 1 to $argc
Local $arg = $CmdLine[$i]
Switch $arg
Case "--file"
$i += 1
$outputFileName = $CmdLine[$i]
Case "--download-wait"
$i += 1
$downloadWaitSeconds = Int($CmdLine[$i])
Case "--cancel"
$cancel = True
Case Else
ConsoleWrite("Unknown option " & $arg & @CRLF)
EndSwitch
Next
EndFunc0