📜 ⬆️ ⬇️

SWAPY with a new code generator

Swapy SWAPY is a graphical UI automation utility for pywinauto (Python).

In version 0.4.7, the code generator has been completely redesigned. The main features, as well as examples of how to quickly and easily create scripts for automatic testing of UI, look under the cat.

Description


SWAPY is a graphical utility for viewing the hierarchy of windows and generating UI automation code for the pywinauto library.

Interface
')
The name itself is an acronym reflecting the basic idea of ​​the application - Simple Windows Automation on PYthon. The utility is a full exe file compiled using PyInstaller. SWAPY does not require any additional installations for automation and code generation. Of course, to continue using the code, you will need to install at least Python and pywinauto. But to test the capabilities and, most importantly, whether such a bundle is suitable for automating your application, SWAPY is completely self-sufficient.

The utility contains three main components:


To create a script, you need to find an element in the tree of all controls and then trigger an action, for example, Click. In this case, the action itself on the object will be executed, and the field with the code will be updated.

In the past, little attention was paid to the code generator. The functions used to search for an element and view its parameters were used more often. All fixes and features for the code generator were added according to the residual principle. As a result, in order to obtain a working code, certain efforts were needed from the side of the user - it was necessary to sequentially initialize all the ancestors.

The new code generator is mostly devoid of the same drawbacks.

The history of development


In early 2011, when he was at the “Automation QA Engineer” position, he discovered the library for automation of UI - pywinauto. On the history of the development of the library itself, you can learn something in the article "Old new pywinauto" . At that time, it was practically not supported. Nevertheless, Pywinauto defeated all its competitors and was chosen to test a number of products with an average complexity of the graphical interface.

I will note the main advantages due to which the choice fell on this option:

  1. The price of the tool. Pywinauto is free, distributed under the GNU LGPL v.2.1 license
  2. This is a python library. With all its features, libraries, etc.
  3. Simple preparation of the environment. Preparing a virtual machine for testing by installing Python + pywinauto is much easier to install, for example, a monster like TestComplete. This is very relevant in the context of using Continuous Integration.

Soon there was one drawback - a lot of time is spent searching for the necessary element and analyzing its properties. There was a lack of a graphical utility for viewing the tree of elements and their parameters. A library for automating graphical interfaces would be nice to have a graphical interface.

It was decided to correct this injustice.

logo-head In April 2011, I began work on the utility, by the end of the year, the version rapidly grew to 0.3.0, and the utility already had all the key components and ... many problems ...

Over the next year, mistakes were slowly corrected and something insignificant was added. And then I changed jobs and the interest to support the utility, which I do not use myself, and even alone, was gone.

SWAPY got its second breath in September 2015, when the guys from pywinauto called for themselves.

Since then, I began to actively develop the utility again. A key improvement is the new code generator.

I revised my attitude to the code generation function as one of the main functions. It is through the code generator that you can acquaint the developer with the additional features of the library, as well as save even an experienced developer from the routine.

New opportunities



Usage example


Now let's create some scripts to automate testing. I tried to choose enough vital examples and at the same time demonstrate the new features of the code generator.

License text


In this test, we verify that the license text is displayed on the About dialog. At the same time, make sure that SWAPY understands that the new window belongs to the old application and will not create unnecessary calls app = Application().Start(...) .

Verify license text
  1. We start manually Notepad ++.
  2. We find in the tree of SWAPY elements the desired menu item and click on it.
    click_menu
  3. To update the tree of elements to display the newly opened window, you need to put a selection on the root element in the tree. At the same time, all child elements will be updated.
  4. We find the About dialog, it’s called Window#657198 , this SWAPY itself formed the name from the handle of the window, since the name was not defined in the usual way ( window.Texts() ).
  5. In the About hierarchy of the dialog, we find the license text and click on it.

    About

    Only the following lines were added:

     window = app.Dialog edit = window.Edit2 edit.Click() #     

    Those. SWAPY used the existing app variable. With the autogeneration code for this test, we are done. Please note that Notepad ++ will be started and closed after the test; the last line of app.Kill_() is responsible for this.

The final test code might look like this:

 from pywinauto.application import Application expected_text = “...” app = Application().Start(cmd_line=u'"C:\\Program Files (x86)\\Notepad++\\notepad++.exe" ') notepad = app[u'Notepad++'] notepad.Wait('ready') menu_item = notepad.MenuItem(u'&?->\u041e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0435...\tF1') menu_item.Click() window = app.Dialog edit = window.Edit2 actual_text = edit.Texts() app.Kill_() assertEqual(expected_text, actual_text) 

As you can see, the minimum of your own code.

Tab order


Let's check the tab movement. We deliberately make a mistake when generating the code and see how SWAPY allows you to remove it.

Check tab order reordering
  1. We start manually Notepad ++.
  2. Open two additional tabs. Find the required ToolBar in the element tree and perform the Click action on the button with the index 0. As a result, the code will appear and one new tab will open.

    add_tab

    We need another tab, repeat the action again. Since the text of the buttons is not available, addressing by index is used. We did not notice and inadvertently clicked on the button with the index 1.

    Added code:

    fix_code
     toolbar_button2 = toolbarwindow.Button(1) toolbar_button2.Click() 

    Need to be corrected. In order not to repeat everything all over again, SWAPY allows you to cancel the last command (you can consistently cancel at least the entire code).

    Clear last command will cancel the last command (the selection) - just what we need. To completely clear the code, there is the Clear the code command. Full cleaning is hidden behind a dialogue with confirmation, in order to avoid accidents at work.

    Now we will do everything right and click on the button with the index 0.

    The code will be added:

     toolbar_button.Click() 

    SWAPY remembers that there is already a toolbar_button = toolbarwindow.Button(0) and you do not need to initialize it to click again.
  3. For drug-n-drop, we use the toolbarwindow.DragMouseInput method. Details of use can be found in the documentation .

    Tab coordinates can be defined using systabcontrol.GetTabRect(0).mid_point()

The test may look like this:

 # automatically generated by SWAPY from pywinauto.application import Application app = Application().Start(cmd_line=u'"C:\\Program Files (x86)\\' u'Notepad++\\notepad++.exe" ') notepad = app[u'Notepad++'] notepad.Wait('ready') systabcontrol = notepad.Tab assertEqual([u'Tab', u'new 1'], systabcontrol.Texts()) toolbarwindow = notepad[u'3'] toolbar_button = toolbarwindow.Button(0) toolbar_button.Click() toolbar_button.Click() assertEqual([u'Tab', u'new 1', u'new 2', u'new 3'], systabcontrol.Texts()) systabcontrol.DragMouseInput( press_coords=systabcontrol.GetTabRect(0).mid_point(), release_coords=systabcontrol.GetTabRect(2).mid_point()) assertEqual([u'Tab', u'new 2', u'new 3', u'new 1'], systabcontrol.Texts()) app.Kill_() 

Here I had to read some documentation and work a bit with the generated code.

Insert and save text


The test requires you to check copying and pasting text and then saving. Let's complicate the task - Notepad ++ is already running and minimized (Minimize), and the standard notepad (from which copy will be made) is just to be launched.

Work with multiple windows
  1. Let's prepare test applications. Run and minimize Notepad ++, and launch a regular notepad with a test file = "notepad check.txt".
  2. In the object tree, find a notepad and click on the contents of the editor.

    notepad

    Please note that the notepad will be run with the original arguments.
  3. Now let's look for Notepad ++ and its text field. You need to remember to deploy it first (Restore).

    restore

    editor

    Everything is going according to plan, but then we suddenly remembered that, by the condition of the problem, Notepad ++ was already running, and our code would try to run it.
    SWAPY by default generates a bunch of app = Application().Start ... app.Kill_() . But in our case, we do not need to run Notepad ++ again.

    The new code generator allows you to change the "approach" to generate code, and this can be done even after the fact.
  4. To change the Application().Start to Application().Connect you need to call the context menu for the Notepad ++ application window and select Application().Connect .

    Connect
  5. Copying and pasting text, we will arrange later, but for now suppose that the text is there and needs to be saved.

    save_as
  6. The “Save as” window has opened, you need to update the element tree in order to see it. To do this, select the root element of the tree. After updating the tree, click on the field with the name of the saved file (to change it later) and click on the button to save.

    accept

All the basic actions are there, now it remains to add sending the commands CTRL + C, CTRL + V and checking to get a real test.
To send commands, use the built-in TypeKeys method.

The full text is below:

 # automatically generated by SWAPY from pywinauto.application import Application import time import os SAVE_PATH = r"Notepad_default_path" app = Application().Start(cmd_line=u'"C:\\Windows\\system32\\notepad.exe" check.txt') notepad = app.Notepad notepad.Wait('ready') edit = notepad.Edit edit.TypeKeys("^a^c") # Copy all the text app2 = Application().Connect(title=u'new 1 - Notepad++', class_name='Notepad++') notepad2 = app2[u'Notepad++'] notepad2.Restore() scintilla = notepad2[u'1'] scintilla.TypeKeys("^a^v") # Paste the text #Save a file menu_item = notepad2.MenuItem(u'&\u0424\u0430\u0439\u043b->\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u043a...\tCtrl+Alt+S') menu_item.Click() window = app2.Dialog edit2 = window.Edit filename = "checked_at_%s" % time.time() # Compose a filename edit2.TypeKeys(filename) button = window.Button button.Click() with open(os.path.join(SAVE_PATH, filename)) as f: assertEqual(“expected_text”, f.read()) app.Kill_() u0424 \ u0430 \ u0439 \ u043b -> \ u0421 \ u043e \ u0445 \ u0440 \ u0430 \ u043d \ u0438 \ u0442 \ u044c \ u043a \ u0430 \ u043a ... \ tCtrl + Alt # automatically generated by SWAPY from pywinauto.application import Application import time import os SAVE_PATH = r"Notepad_default_path" app = Application().Start(cmd_line=u'"C:\\Windows\\system32\\notepad.exe" check.txt') notepad = app.Notepad notepad.Wait('ready') edit = notepad.Edit edit.TypeKeys("^a^c") # Copy all the text app2 = Application().Connect(title=u'new 1 - Notepad++', class_name='Notepad++') notepad2 = app2[u'Notepad++'] notepad2.Restore() scintilla = notepad2[u'1'] scintilla.TypeKeys("^a^v") # Paste the text #Save a file menu_item = notepad2.MenuItem(u'&\u0424\u0430\u0439\u043b->\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u043a...\tCtrl+Alt+S') menu_item.Click() window = app2.Dialog edit2 = window.Edit filename = "checked_at_%s" % time.time() # Compose a filename edit2.TypeKeys(filename) button = window.Button button.Click() with open(os.path.join(SAVE_PATH, filename)) as f: assertEqual(“expected_text”, f.read()) app.Kill_() 


Is it even better?


Of course - Yes!

Even in the described examples, we had to do Click() and then manually change to get the text - Texts() . Or manually added TypeKeys . Future releases have yet to simplify such popular actions by adding additional items to the context menu.

While you can not control the format of access to the elements. Pywinauto allows you to access elements through attributes - window.Edit , and if this is not possible (invalid name for Python variable), then through __getitem__ - window[u'0'] .

SWAPY finds the shortest access name and tries to use it as an attribute. If not, then through __getitem__ . The idea is still the easiest - to get a short code

But, for example, in the “Tab order” test there is such a line toolbarwindow = notepad[u'3'] . Everything works, everything is OK. But imagine, you opened this test after a while, and there is such a magic number. Instead, the troika could be Toolbar - the most understandable, and not the shortest name. The plans are to give the user the opportunity to choose a name (“Name! Name, sister!”).

Also, until you need to update the tree of objects manually. An automatic refresh will obviously add convenience.

useful links



PS


I would like to thank comrades vasily-v-ryabov and airelil for their active participation in the discussion of the features for the new code generator.

Source: https://habr.com/ru/post/270247/


All Articles