In this post, I will try to summarize some of the key areas around Coded UI Test automation on Silverlight DataGrid control.  The content is targeted for QA engineers writing Coded UI Test automation, who I hope will find it useful to  troubleshoot any issues they might come across while automating Silverlight DataGrid, as well as provides few guidelines for the developers of the application under test to ensure the Silverlight DataGrid can be test automated using Coded UI Test framework.  I intend to keep this blog updated as and when I come across new customer issues.

Accessibility

There are certain accessibility requirements for the controls and Coded UI Test makes some strong assumptions to identify the DataGrid control. First and foremost, if the DataGrid is a custom one, ensure that it is derived from DataGrid class. If not, it should at least have an AutomationPeer  associated with it which exposes its control type as AutomationControlType.DataGrid (MSDN link) and implements ITableProvider.

Similarly, for the sub-controls:

Rows – If the DataGrid Row is a custom one, ensure that it is derived from DataGridRow class or at least has an AutomationPeer associated with it which implements ISelectionItemProvider and its selection container is a DataGrid as per the grid assumptions above.

Cells – If the DataGrid Cell is a custom one, ensure that it is derived from DataGridCell class. If not, it should at least have an AutomationPeer associated with it which exposes its control type as AutomationControlType.Custom and implements ITableItemProvider.

If your DataGrid entries are scrollable, the Rows and Groups (in case of grouping of rows) also need to implement IScrollItemProvider.

I will not go into the full details here but just to give a gist of things, Silverlight DataGrids (similar to other Silverlight item container controls) have their AutomationPeers of the items (in this case Rows/Groups; for controls such as List Box, it would be List Item) recycled for performance and optimization purpose i.e. once you get a reference to an AutomationPeer of an item, and it then scrolls out of view, you cannot guarantee the validity of that AutomationPeer. It could be pointing of another item in the view port, or could be completely invalid. Coded UI Test internally uses various heuristics to keep track of AutomationPeers and do re-fetching of these when required. Re-fetching requires scrolling the item into view, which in turn requires IScrollItemProvider support.

UI hierarchy

A typical DataGrid would be having the following UI hierarchy (which has a similar correspondence to its AutomationPeer hierarchy) 

For Datagrid implementing row grouping, they will have additional Group controls encapsulating multiple Rows.

When the DataGrid is virtualized, the groups will just have placeholder (lightweight AutomationPeers) DatagridRow elements.

Rich Properties

Check the links on SilverlightTable, SilverlightRow, SilverlightCell for the list of properties exposed.

Setting the correct Search properties

By default (i.e. the hierarchy that the recorder in Coded UI Test generates), the search hierarchy for a Datagrid Cell include the Row and Table as its ancestors. The search properties of Cell and Row are of more interest since that’s where the performance and resiliency of the playback comes into picture.

The Cell is primarily identified by its ColumnHeader property value obtained from the Cell’s ITableItemProvider. If the ColumnHeader is not defined or not unique, the ColumnIndex is generated as the Cell’s identifiable property. The parent Row’s search properties is either the Automation Id or the Name (in case Automation Id is not defined). If the Row has data bound issues i.e. the Datagrid Rows do not have a proper identifiable Name, it would throw an NotSupportedException during recording mentioning that the Row control does not have any good identification property. The link here gives some guidance on how to associate unique automation property to the DataGrid Rows.

You could safely hand code the search properties of a Cell to include Instance property (for example, in Cell where none of the ColumnHeader or ColumnIndex are defined properly). However, you need to ensure your test automation script is updated accordingly in case of future additions/deletions of columns.

In case of Rows,  it is not advisable to use the Instance property. Additions/Deletions are very much common in Rows and your automation script is bound to fail at some point of time if you rely on the Instance property. Moreover, during playback there is always a bit of search performance overhead while using Instance property.

One final point on the Instance property. The Instance is always generated relative to its immediate parent. So the parent needs to be present in the search hierarchy; for Cell, it is the Row. And for Row? It’s not the Datagrid! It is actually the DatagridRowsPresenter or DatagridRowGroupHeader depending on whether the rows are grouped or not. These two controls are not generated by default in the search hierarchy. So ensure these are included in the search hierarchy in case you persist on setting an Instance property for your Row control.  One of my earlier blogs here mentions how to include intermediate controls in the search hierarchy through hand-coding your test script.

Keeping in mind the above assumptions, your test automation script should work seamlessly across row sorting and column realigning (unless there is a bug floating somewhere!)

Actions on controls within the Cell

Actions on simple control such as Text, Check Box, Radio Button, etc. are recorded on the Cell itself. For more composite controls such as Date Picker, nested DataGrids, etc. actions will be recorded on the target control  though the Cell should ideally be generated as part of the search hierarchy. For example, say there is a Date Picker control contained inside a Cell. The final action will be a SetValue on the Date Picker and the search hierarchy will be

Top Level Window -> {IE search path … } -> DataGrid -> DataGridRow -> DataGridCell -> DatePicker

Suggestions/Workarounds around some queries that customers have commonly reported

  • NotSupportedException thrown during recording – “Last action was not recorded because the control with Name 'ABC' and ControlType 'Row' does not have any good identification property.”

The link here will give guidance on how to associate a searchable property with the DatagridRow.

  • The Rows/Columns are being scrolled during playback or for a simple Cell property validation.

During playback, you might at times observe scrolling of Rows/Columns before performing some action or property validation. (or Groups getting expanded before doing some validation on Rows/Cells inside it). This is currently unavoidable to counter two problems –  the virtualization of controls and the Automation Peer recycling issue which I have explained somewhere above.

  • Search for a Cell is taking a huge amount of time.

If you are hand coding your test script, ensure you search is optimized and has adequate search levels set to restrict the search path.

Something like -

SilverlightDataGridCell myCell = new SilverlightDataGridCell (myDatagridTable);

myCell.SearchProperties.Add(“ColumnHeader”, “ABC”);

 is a bad idea. The search will try to peek into each and every Row and its descendant Cells to find a match. Instead it should include the Row control in between.

SilverlightDataGridRow myRow = new SilverlightDataGridRow (myDatagridTable);

myRow.SearchProperties.Add(“Name”, “XYZ”);

SilverlightDataGridCell myCell = new SilverlightDataGridCell (myRow);

myCell.SearchProperties.Add(“ColumnHeader”, “ABC”);

 Also, the performance of the search is improved considerably if you specify a MaxDepth  property.

myCell.SearchProperties.Add(SilverlightControl.PropertyNames.MaxDepth, 1);

Similar to the caution I had thrown for Instance search property, make sure you know your hierarchy well enough. MaxDepth is relative to the immediate ancestor defined in the search hierarchy. So ensure you have calculated the depth correctly.

  • Action on a control within a Cell fails with FailedToPerformActionOnBlockedControl exception.

The controls within a Cell are not brought into foreground (from element’s Z-index point of view) unless a certain number of clicks are performed on the Cell. During recording, when user performs some action such as clicking on a Check Box or entering some value in a Text inside Cell, the actions are recorded on the Cell. During playback, Coded UI Test implicitly handles the “activation” of the underlying sub-controls before setting the values.

If you are hand coding your own scenario of doing some actions directly on the sub-controls, ensure you have the required “activation” step also included prior to it, such as DoubleClick on the Cell.     

  •  I have a Check Box inside a Cell. I get a null reference exception when I try doing:

SilverlightCheckBox myCheckBoxInsideCell = myRow.Cells[0] as SilverlightCheckBox;

myCheckBoxInsideCell.Checked = true;

SilverlightRow.Cells returns the Cell controls, not the underlying sub-controls. You need to explicitly search within the cells for the sub-controls before performing action on them.

  •  Validation of a DataGridRow inside Data Pager control failed.

You need to iterate through the pages to reach the accurate page where the rows of the DataGrid resides. (You can automate this step since Data Pager is also a supported control type in Coded UI Test). Then add validation for the Row.