Wednesday, May 6, 2020

WinForms DataGridView with Appium/WinAppDriver (Python)

I recently spent some time trying to get the DataGridView of Winforms working (well enough) with Appium. The exposed properties (visible via inspect.exe) are quite minimal. Luckily, there are a couple basics exposed directly as attributes (nothing as nice as selected index though) and with some raw Appium commands/queries, we can expose nicely wrapped methods to use in our automation scripts:

Please view/clone the GitHub repo - you can build the DataGrid sample project easily with .Net Core (you need python, appium modules, Dot Net Core - see python\README.md for details):

cd winforms\datagrid
dotnet restore
dotnet build

cd python
python datagrid_sample.py

A Reusable Python DataGridView Helper

The appium_datagridview.py wraps a Winform's DataGridView. It does this by taking a python object that wraps object locating in constructor. It then attempts to acquire the WebElement. Once found, it verifies it is expected control type (Table). The class then exposes a few helper methods:
  • row_count()
  • col_count()
  • send_key_repeated()
  • selected_row()
  • visible_rows()
  • move_to_row()
The DataGridView may be one of the more complex Winform objects, so this class may not work for all layouts/settings. However, should be a decent base to extend to handle most/all settings of the Grid control. The first few methods listed above are pretty simple and use the built in attributes/methods and do not add much.

However, with selected_row(), we start to get a little tricky in getting the selected row (again, there is no actual property for this). We send a SHIFT key to the grid control - this will focus the grid without changing the selected row). This action will end up with the cell/row becoming the active web element (if it wasn't already). We could be in a cell/row/etc based on your grid. If the focused item is not what appears to be a row, the code makes use of the active elements RuntimeId and some XPath to find the parent row element.

The visible_rows() method finds all Rows using XPath, and loops through them to decide if they appear visible or not. It bases this on have a valid y value. Of screen rows have negative values. Worth noting, that a row may be half on and half off (so height of element can change from scroll).

To select a row via Appium, I created move_to_row(). Initially, I planned to use mouse scrolling and scrollbar. Though, sending key presses turned out to be much simpler, and the DataGridView auto scrolls to the current row once you start sending key presses (Home, End, Right, Left, Up, Down, etc). So, much easier to send X Up/Down keys in one go to get both selection and ensure the row is visible. A neat little trick in python is to multiple a string by a value to repeat it X times (also, it is faster with Appium to send one string of all Key Chars then to send one string multiple times). Hence, this method uses the selected_row() method to find current row - decides if we need to go up or down, and sends the keys.

Getting this working took quite a bit of experimenting. And, I'm sure there are flaws with different Grid settings. Hoping this might be useful to others as a starting point. Leave a comment if you find this useful - or fork the repo and extend it as you desire. Would love to hear success stories. And, building up a python appium framework for Winforms Automation testing would benefit quite a few people.


Sunday, April 19, 2020

Refactoring a Legacy .Net/C# - Replace Service Locator with Dependency Injection (Autofac) - Without breaking old code


I recently had the opportunity to add Dependency Injection into a 25yr old .Net Application. Now, I'm sure your saying to yourself, ".Net hasn't been around 25 yrs!"... well this application started life with C++/MFC (consisting of a few hundred CDialogs). Then, 15 years ago, it was halfway ported to C#/Winforms keeping most all Dialogs as CLI/MFC with some re-done in C#.

During this port, a home brew Inversion of Control (IoC) was created. This used the service locator (SL) pattern - basically, you ask it for an interface/type/service and it returns to you one that was registered to the system. Based off the framework's built-in IServiceProvider class. Now, this class doesn't do a whole lot, basically just provides a GetService(Type) method

There are many articles on pros and cons of DI vs SL supporting one or the other. But, I believe today's view is that SL is an anti-pattern (one example, Wikipedia page) because it hides you from knowing what dependencies are needed by the class, and hopefully they are registered with the system properly when calling the class. Although, pretty much all IoC's supporting injection also support manually resolving a reference - some times, you just have to do things not pretty 😀.

With the SL system in our app, I've found that Unit Testing is always extremely very tedious, and error prone. Partly because of the design of our system, also because you do not know what dependencies all the classes require - so mocking them is error prone. I can say passing all needed interfaces as part of a constructor certainly makes unit test setup easier. And during actual application runtime, have the IoC system inject all the services and manage lifetimes of shared resources is great.

So, my first goal was to find a modern, open source (having code is great when a tech becomes no longer popular/supported) IoC/DI platform for .Net. My second goal, was being able to have both the existing SL and the new DI system peacefully coexist. Needed to not break thousands of lines of code leading to runtime crashes trying to resolve dependencies.

After a bit of research, settled on Autofac. It had nice syntax, plenty of flexibility, and checked all the boxes.

Having old SL types resolve in new system (for both DI and SL)

Autofac supports numerous ways to register types into its registry. In this case, I already had a SL that could be use to find types, so the best way to get Autofac to see these types was to use RegisterSource builder method. Now, old resolver services can be Injected when Autofac creates services. Any types registered to Autofac are not found in older resolver (intentionally - moving away from it). And all existing code works without change. Take a look at the code below:

 

Tuesday, April 14, 2020

Retrospective

It has been a few years since I've had any kind of blog (10yrs+). The last info posted was really about OIS work. That has since been released to other maintainers.

Been an interesting and busy decade: raising two children - as well as a couple dogs (and several fish) - learning guitar, growing professionally, purchasing a house, a few road trips, and mostly enjoying life.

Has also been quite some time since I have contributed anything substantial to Open Source - though, I do make use of many tools, projects, Python/Nuget/NPM/etc modules, frameworks - every single day in my day job. Looking to get back into the area of contributing to Open Source as well as perhaps helping aspiring developers get started.

Looking to start small, for now, as time permits. Am looking to contribute back by writing various software articles on topics that have great interest to me or things that I have had to spend time researching and have a solution for that could save others time (topics such as business software, game dev, automation, python, dotnet core, etc).

With the recent turn in world events - that is both scary and unsettling - both in terms of family and friends' health, as well as everybody else's health - please stay safe out there! But also in terms of the economic health and everybody's financial future. Though, while on lock down in the Golden State, definitely will make full use of the time provided to get back in the game.

WinForms DataGridView with Appium/WinAppDriver (Python)

I recently spent some time trying to get the DataGridView of Winforms working (well enough) with Appium. The exposed properties (visible via...