Usage

Coordinator
Feb 13, 2012 at 7:59 PM

Questions about use of the framework

Feb 26, 2012 at 7:33 AM

How can I set a textbox value? I try this:

 

$EntTab | Get-UIATextBox -AutomationID 'PART_TextBox' | Invoke-SetUIAControlText 'VCRDesign04'

 

It can find the textbox but doesn't set the value.

Coordinator
Mar 1, 2012 at 4:17 PM
behradz wrote:

How can I set a textbox value? I try this:

$EntTab | Get-UIATextBox -AutomationID 'PART_TextBox' | Invoke-SetUIAControlText 'VCRDesign04'

It can find the textbox but doesn't set the value.

Hello behradz,

setting text into a textbox/edit can work strangely in many cases:

autocomplete is on may require you to input characters one by one (it heavily depends on the way programmers use event's invoking)

About the input: 

1) Invoke-SetUIAControlText - do you have rather old version of the module?

maybe, you wanted to write: Invoke-UIAControlText (seemingly it might be called so before I rearranged names againts Automation patterns) or Set-UIAControlText?

2) for example, Quest PowerGui 3.2 ScriptEditor -> the textbox in the toolbar where the user can type in script paramaters:

Get-UIAWindow -ProcessName scripteditor_x86 | Get-UIATextBox -AutomationId 132468 | Set-UIAControlText -Text 'text to the text box'

(through Win32)

another example is a simple winforms app (just one text box on the form):

Get-UIAWindow -n as | Get-UIATextBox -au textBox1 | Set-UIATextBoxText -Text "asdfasdf"

please notice the need to explicitly set the -Text parameter, because default parameter is -InputObject (AutomatinElement)

3) in many cases setting the text may work bad as MS UIautomation is not exceptionally good on it

Our framework provide you with the following debugging means:

the -Verbose parameter that can be set exactly to the cmdlet whose work is dubious (it's used in our tests of cmdlets)

the UIAutomation.log in the user's %TEMP% (you need quit the powershell session to read it).

Mar 1, 2012 at 7:16 PM

Thanks for your very elaborated description. Now I have it working. I have a problem though:

UIAutomation is very slow. I think the reason is that we are not reusing the objects and load them for every transaction (change or search). Is there any way we can reuse the main object?

Coordinator
Mar 1, 2012 at 7:55 PM
Edited Mar 1, 2012 at 8:59 PM
behradz wrote:

I have a problem though:

UIAutomation is very slow. I think the reason is that we are not reusing the objects and load them for every transaction (change or search). Is there any way we can reuse the main object?

Are you speaking about the PowerShell module or the MS UI Automation library? Even the library is slower that direct using of Win32 API...

Okay, let's try to find out what can be an impediment to the product speed.

1) first and foremost, there is possibly the stupid default setting: [UIAutomation.Mode]::Profile -eq [UIAutomation.Modes]::Presentation

This sets 500 milliseconds' timeout on every successful or failed operation. Why? This is THE presentation mode: highlighted controls, slow motion and so on, so that any IT boss could run it with minimal efforts and present to himself/herself and to some higher personnel. :)

Maybe, this is wrong default, but ANY default is not good unless I publish the User Guide. :) I'll be trying out something this week.

There is the cmdlet that display module settings: Show-UIAModuleSettings and there is a very primitive system of profiles (or modes):

  [UIAutomation.Mode]::Profile = [UIAutomation.Modes]::Presentation/Normal/Debug

Try the Normal mode, it has no delays (and no highlight) or set settings by hands: [UIAutomaiton.Preferences]::... .

2) The Log. The enormous log. There are no log levels for now, so that you can simply switch it off: [UIAutomation.Preferences]::Log=$false

3) Highlighter can be switched off too: [UIAutomation.Preferences]::HIghlight=$false;

4) OnSuccessAction, OnErrorAction, OnSleepAction: they couldn't be switched off right now, but I'll add the possibility to switch them off the next release (a small help but...)

5) If you don't need in filtered errors, you may decrease the number of saved errors: [UIAutomation.Preferences]::MaximumErrorCount, a small time saving though.

6) [UiAutomation.Preferences]::OnErrorScreenshot=$false also saves your script's time.

After you did or didn't apply the settings above, now I'm ready to discuss how the module works, and why, and what is possible to do.

Coordinator
Mar 1, 2012 at 8:10 PM
Edited Mar 1, 2012 at 8:26 PM

Despite many tricks we can or can't implement in the code, we must pay for pipelining, code reuse, encapsulating, the code divided into cmdlets with user-friendly names and so on...

1) The user issues the command: Get-UIAWindows -p $processname

the AutomationElement is automatically saved to the [UIAutomation.CurrentData]::CurrentWindow variable.

Unless you are testing a wizard, especially setup wizard, and unless your application often emits dialogs, windows or similar, you further can work with controls without getting the window again and again:

Get-UIAMenuItem -n ... | Invoke-UIAMenuItemClick; Get-UIAButton | Invoke-UIAButtonClick; ...

This is the same (internally) as 

[UIAutomation.CurrentData]::CurrentWindow | Get-UIAMenuItem -n ... | Invoke-UIAMenuItemClick;

[UIAutomation.CurrentData]::CurrentWindow | Get-UIAButton | Invoke-UIAButtonClick;

We can't repeat this trick with a control because there would be a mess (an implied control? noooo)

2) For now almost all the query are done using TreeScope.Subtree (or Descendants) search. It's possible to do (for advanced users) the deep of search parametrized, as Get-UIAButton -Scope Children/Descendants; Get-UIAPane -Scope Children/Ancestors, etc.

3) There are several cmdlets that allow you to do simply things without usual usability like Get-UIA[ControlType]

They are: Get-UIAControlChildren (search only among immediate children), Get-UIAControlParent (the immediate parent), maybe some else that right now are out of my near memory... :)

4) using of variables similarly to the sample of your code:

$tabitem = (Get-UIAWindows -n .... | Get-UIATabItem);

Get-UIATextBox -InputObject $tabitem -au ...;

Get-UIAButton -InputObject $tabitem -n OK;

 

However, MS UiAutomation will never so fast as SendMessage/PostMessage, and the PowerShell module will never faster than bare MS UIAutomation, I hope there is the place for further optimization... Just when more and more people will demand the speed, we'll have to optimize it in some way.

On the other hand, the usability for everyone that powershell provides (pieces of code called cmdlets are much more useful than dotten notation, to me.) and the ability to run almost everything without writing much code (click a control and check a Windows or web service from the same script just in several lines of code), and the fact that pieces of code (cmdlets) are tested as pieces of code, these advantages should win. ;)

Mar 1, 2012 at 8:59 PM

You work is really appreciated and I understand what you say but the speed could be a real issue as this is to be used for automated testing of hundred of test cases.

probably it can work faster by caching ui elements inside the main object.  You can see my code below and let me know if there are better way of doing it. As you see I am caching the MainWindow but if it could cache the visual tree inside it, it could go much faster.

 

The other thing is that there were no method to find the UIAutomation object by processID or mainWindow hwnd. You may like to consider adding it.

 

--------------------------------------------------

$Process=Start-Process -FilePath "C:\Config.exe"
sleep 2


[Reflection.Assembly]::LoadFile('C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationClient.dll')[Reflection.Assembly]::LoadFile('C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll')


$MainWindow=[Windows.Automation.AutomationElement]::FromHandle($Process.MainWindowHandle)


$EntTab=     $MainWindow  | Get-UIATabItem    -AutomationID 'tabENT' -Title 'Enterprise'

$DBServer=       $EntTab  | Get-UIATextBox    -AutomationID 'PART_TextBox'

$DBAuth=         $EntTab  | Get-UIATextBox    -AutomationID 'PART_EditableTextBox'

$DBPassword=     $EntTab  | Get-UIAEdit       -AutomationID 'passwordBox'

$DBDatabase=     $EntTab  | Get-UIAText    -AutomationID 'txtDatabaseName' | Get-UIATextBox -AutomationID 'PART_TextBox'

$btnDBConnect=$MainWindow | Get-UIAButton -AutomationID 'btnGetValues'

$PortsTab=   $MainWindow  | Get-UIAGroup  -AutomationID 'gboxPorts'

$PortBase=     $PortsTab  | Get-UIAText   -AutomationID 'txtBasePort'            | Get-UIATextBox -AutomationID 'PART_TextBox'

$PortSecurity= $PortsTab  | Get-UIAText   -AutomationID 'txtAuthenticationPort'  | Get-UIATextBox -AutomationID 'PART_TextBox'

$PortMigration=$PortsTab  | Get-UIAText   -AutomationID'txtDMPort'              | Get-UIATextBox -AutomationID 'PART_TextBox'

$PortPersonal= $PortsTab  | Get-UIAText   -AutomationID 'txtPersonalizationPort' | Get-UIATextBox -AutomationID 'PART_TextBox'

$btnExit=    $MainWindow  | Get-UIAButton -AutomationID 'PART_Button' -Title 'Exit'

$btnSave=    $MainWindow  | Get-UIAButton -AutomationID 'PART_Button' -Title 'Save Configuration'

$btnReset=   $MainWindow  | Get-UIAButton -AutomationID 'PART_Button' -Title 'Reset'

 

Coordinator
Mar 1, 2012 at 9:25 PM

1) Start the script with setting the Normal mode:

[UIAutomation.Mode]::Profile = [UIAutomation.Modes]::Normal

or set zero delays manually:

[UIAutomation.Preferences]::OnSuccessDelay=0

[UIAutomation.Preferences]::OnErrorDelay=0

[UIAutomation.Preferences]::Log=$false

2) You can rewrite the beginning in the following way:

Start-Process -FilePath "C:\Config.exe"

$MainWindow=Get-UIAWindow -ProcessName Config -Seconds 20;

comments: we hate sleeps! It's always bad, it and SendKeys, the thing you are never sure in. Get-Window/Control and Wait-ControlIsEnabled cmdlets are born with the cycle inside. They will wait the time you set through the -Seconds [sec] or -Timeout [millisec] parameters.

The only problem is some wizards, where after pressing the Next button, the handle of the main window changes.

The code above will wait for a window belonging to the process you chose. If no window will be available after the time, it'll return an exception.

3) If you window is stable (no unexpected child windows will appear), you are free to omit the main window variable:

$btnExit=    Get-UIAButton -AutomationID 'PART_Button' -Title 'Exit'

$btnSave=    Get-UIAButton -AutomationID 'PART_Button' -Title 'Save Configuration'

$btnReset=   Get-UIAButton -AutomationID 'PART_Button' -Title 'Reset'

$MainWindow -eq [UIAutomation.CurrentData]::CurrentWindow

 

 

Coordinator
Mar 1, 2012 at 9:55 PM

Seemingly, getting a lot of various windows is a rare case. So that the user will not need getting a window by PID very often. Since this is not a frequent need, nothing prevents the user from issuing the command:

Get-UIAWindow -p (Get-Process -Id 1234).ProcessName | Get-UIAsomething ...

Thsi module contains three hundred of cmdlets (eightly or ninety percent of them are generated only for usability:)), some of them contain parameters that are taken from the mind, not from the MS guidelines. Why do the life of the user more complicated, inventing (sometimes controversial) cmdlets and parameters? Get-Process is enough, the good thing by MS.

Now about handles. 

1) not every control has a handle. Grid items, actions at the right of MMC 3.0, menu items, many ocntrol can have no handle at all. 

The ideology of the module is to use Win32 (read: heavily related to using a handle) functions only when it's unavoidable.

For example, UI Automation failed to click a control or to run its context menu. There rule handles and Win32 API (Invoke-UIAControlClick, Invoke-UIAControlContextMenu are the thing I uses rather often with some controls). Even more, you can pipe a handless control to it, and Win32 cmdlet calculated x and y from an ancestor that has a handle and clicks a handless control...

2) What do you plan to optimize? Time? How?

(Get-UIAsometing).Current.NativeWindowHandle | Get-UIAcmdletThatConsumesHandle

What is the idea if you use here the same Get-UIAsomething? no time saving, in my opinion.

Please give me more information about how you would use handles?

3) About your code: why do you do so many Get-?

If you need to check that there are these controls that you've listed in the code (unit tests that check that the form showed all the controls?), consider using ControlState cmdlets:

run your application and run the following:

Get-UIAWindow ... | Get-UIAControlChildren/Descendants | ConvertTo-UIASearchCriteria

It'll return hashtables as powershell text: @{Name="...";AutomationId="textBox1";....}

After that select all of them that you need, Next cut out all unnecessary like ProcessId, NativeWindowHandle (different on any new run).

After all you'll have something like:

Test-UIAControlState -SearchCriteria @{AutomationID='PART_Button';Name='Exit'},@{Name='Save Configuration';IsEnabled=$true},@{},@{}

If the cmdlet finds ALL the controls that match hashtable (i.e. the 'Save Configuration' button is enabled and the Exit button has the 'PART_Button' automationid), it'll return $true. Ontherwise (i.e. even only one conditions is wrong), $false.

As I can see, you use not the last version? the -Title parameters is an alias, instead please start using -Name (it replaces the first).

 

Thanks you for feature proposals and the use of the module.

Coordinator
Mar 1, 2012 at 10:02 PM

To conclude, you can try to produce almost an one-liner (specifically to your example that only Get-s control the most of the time) or several one-liners:

Start-Process -FilePath "C:\Config.exe"

Get-UIAWindow -ProcessName Config -Seconds 20 | Test-UIAControlState -SearchCriteria `

@{Name='button name'}, @{automationid='....';Name='some name'}, @{controltype='button';isoffscreen=$false;classname='.....';name='...'}, ....

Test-UIAControlState and Wait-UIAControlState (do it in the cycle until timeout expires) return $true if all the conditions are $true (one control per one hashtable).

Mar 2, 2012 at 7:10 AM

Thanks for your help. I have to apply what you said but about find by processID, I don't think your solution work for me and many other people as well. As a QA it is not wise that you find a process by name at all! That can be another process and totally mess up your tests reliability. I want to start a process for myself and make sure I am dealing with that instance. So I propose adding following two switches to Get-UIAWindow:

 

Get-UIAWindow -Process $ProcessObject

Get-UIAWindow -ProcessID $ProcessID

Get-UIAWindow -WindowHand $hwnd

Jun 28, 2012 at 7:41 AM
Edited Jun 28, 2012 at 10:20 AM

Please, give some examples how to wait for new window with unpredictable Name value(it can be various error or notify messages)?; how to enumerate all opened windows? I want to remove windows to clean desktop after unsuccessful run of application.

Coordinator
Jun 28, 2012 at 11:42 AM

Hello JUSTASM,

1) how to wait for a new window with random Name?

first of all, there are ways to take a window by its ProcessId/pid, ProcessName/pn and its process object. If you issue the process, all the following would work:

# you've set a name for process somewhere in the code
$processName = "calc";

# get the window by its process id
Get-UIAWindow -pid (Start-Process $processName -PassThru).Id

# get the window by its process name
Get-UIAWindow -pn (Start-Process $processName -PassThru).ProcessName

second, you also can use a part of its name (supposing that some part of a name is uncahngeble from run to run);

Start-Process calc; Get-UIAWindow -n *lato*

Note that rare windows that are not seen by MS Automation and could be found by using FindWindow, still don't support wild cards (it will be done relatively soon). I know only one such an example: on a 64-bit OS, a 32-bit MMC snap-in that is not shown in the TaskBar, runs its own window instead of the main MMC window (It's really rare case).

 

2) Which opened windows do you want to enumerate?

2.1) window with known to you a part of name:

Start-Process calc; Start-Process calc; Start-Process calc;
Get-UIADesktop | Get-UIAControlChildren -ControlType Window | ?{$_.Current.Name.Contains("alcu")}

2.2) if you need to collect all the dialog of the application under test, there is search down the Automaiton tree

# you should run PowerShell as Asministrator to reproduce this test
Start-Process services.msc -PassThru | Get-UIAWindow | Get-UIAMenuItem -Name File | Invoke-UIAMenuItemClick | Get-UIAMenuItem -Name Opt* | Invoke-UIAMenuItemClick;

# now we have several windows in the process mmc: the main window, the Options window 
# and one or more internal frames of mmc itself
# as mmc has windows called snap-ins (or possibly even more windows I'm not aware of)

# in the code below, you don't need to use the Get-UIAWindow cmdlet
# because the last window you've successfully gotten
# stored in the [UIAutomation.CurrentData]::CurrentWindow variable
Get-UIAControlDescendants -ControlType Window | %{$_.Current.Name;}

# in this case, we have gotten only two windows, both with names
# in more complicated cases, you need to use several properties to select a window
Get-UIAControlDescendants -ControlType Window | %{Write-Host "$($_.Current.Name)`t$($_.Current.AutomaitonId)`t$($_.Current.ClassName)";}

there also are Search- cmdlets, but these are considered legacy as they use TreeWalker what means more cross-process callc during the test:

# the search for a specified window
Search-UIAControl -Name Options -ControlType Window
# the code assumes that you already have the main window
# and the same as the code:
[UIAutomation.CurrentData]::CurrentWindow | Search-UIAControl -Name Options -ControlType Window
As can be seen, the Search-UIAControl cmdlet is more specific (like every good search), whereas Get-UIAControlDescendants returns all that is of type specified.

2.3) if you need to catch inexplicable windows like a dialog in the process of the application under test, use the Register-UIAWindowsOpenedEvent cmdlet (it's recommended to use also the Unregister-UIAEvent cmdlet from time to time during the test suite):

Start-Process services.msc -PassThru | Get-UIAWindow | Register-UIAWindowOpenedEvent -EventAction {param($src, $e) [System.Windows.Forms.MessageBox]::Show("A new window is open: $($src.Cached.Name)");};
Get-UIAMenuItem -Name File | Invoke-UIAMenuItemClick | Get-UIAMenuItem -Name Opt* | Invoke-UIAMenuItemClick;

2.4) finally, if you need to catch a windows of another process that appears during the test, use the standard Register-WMIObjectEvent cmdlet (there are a lot of ways to write the code as parameters of the cmdlet can be used in various combinaitons):

the smaple from Richard Siddaway's Blog: http://msmvps.com/blogs/richardsiddaway/archive/2009/11/07/powershell-wmi-events.aspx

3) you can eliminate processes, it's the best choise in many situations

Start-Process calc; Start-Process calc; Start-Process calc;
Stop-Process -Name calc
However, some applications should save their data or close connections before exiting, so that you need to use something like in the item 2.1 (Get-UIADesktop | Get-UIAControlChildren: main windows are always children of the desktop) to get all of them to perform exit via main menu or hitting the Close button...