Scope

This blog is for python fans.

With Ranorex 3.0 support for IronPython was dropped, because as a dynamic language the technical solutions were always a little different than with C# or VB and that produced extra effort, which was not justified by the number of those using IronPython.

Nevertheless Ironpython is a very flexible scripting language, which in addition has the full .NET Framework API and, for testing, the Ranorex API at its disposal.

Ranorex Studio Projects produce .NET assemblies. So the modules and classes therein can be accessed through IronPython.

IronPython in Combination with Ranorex Studio

One could use Ranorex Studio to do the recordings and make the rest in IronPython. With Python one can do if branches and arbitrary loops, which is not possible in the Ranorex Test Suite.

Here is a little example that uses a Ranorex Testsuite Project assembly (executable). The same one can do with a Ranorex Test Module Library assembly (dll).

import clr
import sys

#replace A_User and A_TestProject
RanorexPrjPath = 'C:UsersA_UserDocumentsRanorexRanorexStudio Projects'
sys.path += [RanorexPrjPath+'A_TestProjectA_TestProjectbinDebug']
clr.AddReference('A_TestProject.exe')
##or
#clr.AddReferenceToFileAndPath(RanorexPrjPath+'A_TestProjectA_TestProjectbinDebugA_TestProject.exe')

import A_TestProject
A_TestProject.openvip.Start()

More about this can be found in this blog dealing with VS2010 interoperability.

Getting Data

The data handling that is done in the test suite one can do in Python, either using Ranorex classes or using IronPython directly or using .NET with IronPython.

Ranorex DataConnector:

clr.AddReference('Ranorex.Core')
import Ranorex
connector = Ranorex.Core.Data.CsvDataConnector('vips','C:tempvips.csv',True)
cache = Ranorex.Core.Data.DataCache(connector)
context = Ranorex.Core.Data.DataContext(cache,None)
while context.Next():
    TestForIman.AddVIP.Instance.firstname = context.CurrentRow.Values[cache.Columns.IndexOf('first name')]
    TestForIman.AddVIP.Instance.secondname = context.CurrentRow.Values[cache.Columns.IndexOf('secondname')]
    TestForIman.AddVIP.Instance.category = context.CurrentRow.Values[cache.Columns.IndexOf('category')]
    TestForIman.AddVIP.Instance.gender = context.CurrentRow.Values[cache.Columns.IndexOf('gender')]
    TestForIman.AddVIP.Instance.count = context.CurrentRow.Values[cache.Columns.IndexOf('cnt')]
    TestForIman.AddVIP.Start()

IronPython alone:

f = open('C:tempvips.csv')
lines = f.readlines()
columns = [c.strip() for c in lines[0].split(';')]
for r in lines[1:]:
    values = [v.strip() for v in r.split(';')]
    TestForIman.AddVIP.Instance.firstname  = values[columns.index('first name')]
    TestForIman.AddVIP.Instance.secondname = values[columns.index('secondname')]
    TestForIman.AddVIP.Instance.category   = values[columns.index('category')]
    TestForIman.AddVIP.Instance.gender     = values[columns.index('gender')]
    TestForIman.AddVIP.Instance.count      = values[columns.index('cnt')]
    TestForIman.AddVIP.Start()

An MS Access table:

clr.AddReference("System.Data")
from System.Data import DataSet
from System.Data.OleDb import OleDbConnection, OleDbDataAdapter

conStr = 'Provider=Microsoft.Ace.OLEDB.12.0; Persist Security Info = False; Data Source=C:tempvip.accdb;'
con = OleDbConnection(conStr)
con.Open()

query = 'SELECT * FROM vips'
adapter = OleDbDataAdapter(query, con)
ds = DataSet()
adapter.Fill(ds, 'vips')

vips = ds.Tables['vips']

columns = [c.ColumnName for c in vips.Columns]

for r in vips.Rows:
    values = list(r.ItemArray)
    TestForIman.AddVIP.Instance.firstname  = values[columns.index('first_name')]
    TestForIman.AddVIP.Instance.secondname = values[columns.index('second_name')]
    TestForIman.AddVIP.Instance.category   = values[columns.index('genre')]
    TestForIman.AddVIP.Instance.gender     = values[columns.index('gender')]
    TestForIman.AddVIP.Instance.count      = values[columns.index('count')]
    TestForIman.AddVIP.Start()

An Excel Table:

import clr
clr.AddReferenceByName('Microsoft.Office.Interop.Excel, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c')
from Microsoft.Office.Interop import Excel

ex = Excel.ApplicationClass()
workbook = ex.Workbooks.Open('C:tempvips.xlsx')
ws = workbook.Worksheets[1]
cells = ws.Range['A1','E3']
columns = [cells.Value2[0,i] for i in range(5)]
for j in range(1,3):
    values = [str(cells.Value2[j,i]).split('.')[0] for i in range(5)]
    TestForIman.AddVIP.Instance.firstname  = values[columns.index('first name')]
    TestForIman.AddVIP.Instance.secondname = values[columns.index('secondname')]
    TestForIman.AddVIP.Instance.category   = values[columns.index('category')]
    TestForIman.AddVIP.Instance.gender     = values[columns.index('gender')]
    TestForIman.AddVIP.Instance.count      = values[columns.index('cnt')]
    TestForIman.AddVIP.Start()

Directly using the Ranorex API in IronPython

Ranorex Modules Needed

Here we don’t use recordings created by Ranorex Studio, but rather create code similar to that created for recordings. First we import the needed modules. Also one needs to make sure that sys.path contains the folders where the used assemblies are.

Here are the modules usually needed.

import clr
import sys
sys.path= ['.', 'c:Program Files (x86)Microsoft Visual Studio 10.0VC', 'C:Program Files (x86)IronPython 2.7Lib', 'C:Program Files (x86)IronPython 2.7DLLs', 'C:Program Files (x86)IronPython 2.7', 'C:Program Files (x86)IronPython 2.7libsite-packages','C:Program Files (x86)Ranorex 3.0Bin']
clr.AddReference('System')
clr.AddReference('System.Core')

clr.AddReference('Ranorex.Core')
clr.AddReference('Ranorex.Plugin.Flex')
clr.AddReference('Ranorex.Plugin.MozillaWeb')
clr.AddReference('Ranorex.Plugin.Msaa')
clr.AddReference('Ranorex.Plugin.Office')
clr.AddReference('Ranorex.Plugin.RawText')
clr.AddReference('Ranorex.Plugin.Web')
clr.AddReference('Ranorex.Plugin.Win32')
clr.AddReference('Ranorex.Plugin.WinForms')
clr.AddReference('Ranorex.Plugin.Wpf')

import Ranorex
import System

Adapter Class

We can now access the elements using the adapter classes. Basically the same as one would do with C#, just with Python syntax. ::
bt = Ranorex.Button("/form[@controlname=’formVipApplication’]/button[@controlname=’btAdd’]")
bt.Click()

Iterating through Elements

We can iterate through elements that we get through Find().

vipadapter = Ranorex.Form("/form[@controlname='formVipApplication']")
vip_elements = [c for c in vipadapter.Find(".//*")]

The Element

Find will return Ranorex.Core.Element classes, which we can cast to adapter classes. ::
nw = vip_elements[0].As[Ranorex.NativeWindow]()
print nw.Class
li = vip_elements[61].As[Ranorex.ListItem]()
print li.Text
#invoke actions:
li.Select()
But we can also immediately use the Element class, because internally the adapter class forwards to the element class, anyway. ::
attr = lambda e,a: e.GetAttributeValue(a)
emptyArray = Array[str]([])
action = lambda e,a: e.InvokeAction(a, emptyArray )
print attr(vip_elements[0],’Class’)
print attr(vip_elements[61], ‘Text’)
#invoke actions:
action(vip_elements[61], ‘select’)

Actually one doesn’t need the adapter class at all.

The Element class has a link to the class that does the work to extract attributes, execute actions and intercept events. To select one of these attributes, actions or events not methods but strings are used. We can use the string itself to access attributes or actions, as shown in the previous example.

To get these strings we go via capabilities. They consist of three lists, one of attribute strings, one of action strings, one of event strings. As a side note: For every such capability there is an adapter class.

element = vip_elements[0]
caps = element.Capabilities
mkstr = lambda c: map(str,c)
caps_as_string = mkstr(caps)
attributes_of_first_capability = mkstr(caps[0].Attributes)
all_attributes = dict([(str(caps[i]),mkstr(caps[i].Attributes)) for i,c in enumerate(caps)])
all_actions = dict([(str(caps[i]),mkstr(caps[i].Actions)) for i,c in enumerate(caps)])
all_events = dict([(str(caps[i]),mkstr(caps[i].Events)) for i,c in enumerate(caps)])

The Element Tree

The Element also holds together the element tree. This is the root element.

rootelement = Ranorex.Core.ElementEngine.Instance.RootElement

When we try to find a RanoreXPath starting from this element we use the absolute path.

vipelement = rootelement.FindSingle("/form[@controlname='formVipApplication']")

To find an element starting from vipelement we use a relative path.

statustext = vipelement.FindSingle("statusbar/text[@accessiblename='VIP count: 0']")

rootelement.Children gives us the top level children. We could walk the whole element tree like this.

def traverse_children(element):
    yield element
    for c in element.Children:
        for cc in traverse_children(c):
            yield cc
tree = traverse_children(rootelement)
tree.next()#rootelement
tree.next()#first application
tree.next()#first element in application
...
#this could take long: treelist=list(tree)

The recorder finds the element from the screen position.

element = rootelement.FindFromPoint(Point(5,5))

Since I have my task bar vertically on the left I find the Windows Start button.

If we have an element we can create a path for it either using the Simple or Reduce method.

path = element.GetPath(Ranorex.Core.PathBuildMode.Reduce)

Using the recorder from within IronPython

Actually IronPython is still in the API. Its just not exposed in Ranorex Studio.
Is it possible to record for example into the clipboard and then paste the code into our IronPython script.

It is possible.

In the following code we do this:

  • Initialize the GUI with Stop, Play and Save button. It loads an existing repository. Then we start recording.
  • Press Stop to stop the recording
  • Press Play to immediately replay the recording
  • Press Save to save the repository as xml and convert it into Default.py. The recording is converted to IronPython and saved in the clipboard.

The recording code in the clipboard is a full IronPython module, as it was used in Ranorex Studio.
We can paste it from the clipboard into our script, keep the actions in the Run() and remove the rest.

We can also save it into a file and start it like this.

import myrecord
myrecord.Start()

I had to remove the self.Init() line from the Run() to make it work this way.

Note

Since IronPython is not part of Ranorex Studio any more you can expect that code generation gets outdated.

Here is the IronPython code for this primitive recorder.

#header
import clr
import sys
sys.path= ['.', 'c:Program Files (x86)Microsoft Visual Studio 10.0VC', 'C:Program Files (x86)IronPython 2.7Lib', 'C:Program Files (x86)IronPython 2.7DLLs', 'C:Program Files (x86)IronPython 2.7', 'C:Program Files (x86)IronPython 2.7libsite-packages','C:Program Files (x86)Ranorex 3.0Bin']
clr.AddReference('System')
clr.AddReference('System.Core')

clr.AddReference('Ranorex.Core')
clr.AddReference('Ranorex.Plugin.Flex')
clr.AddReference('Ranorex.Plugin.MozillaWeb')
clr.AddReference('Ranorex.Plugin.Msaa')
clr.AddReference('Ranorex.Plugin.Office')
clr.AddReference('Ranorex.Plugin.RawText')
clr.AddReference('Ranorex.Plugin.Web')
clr.AddReference('Ranorex.Plugin.Win32')
clr.AddReference('Ranorex.Plugin.WinForms')
clr.AddReference('Ranorex.Plugin.Wpf')

import Ranorex
import System

#plus additional references
clr.AddReference('System.Drawing')
clr.AddReference('System.Windows.Forms')
clr.AddReference('Ranorex.Controls.dll')

from System.Drawing import Point
from System.Windows.Forms import Application, Button, Form

import os
import os.path
cwd = os.getcwd()

class HelloWorldForm(Form):
    def __init__(self):
        self.Text = 'Ranorex Recording'

        stop = Button()
        stop.Text = "Stop"
        stop.Location = Point(10, 10)
        stop.Click += self.stopPressed
        self.Controls.Add(stop)

        play = Button()
        play.Text = "play"
        play.Location = Point(10, 40)
        play.Click += self.playPressed
        self.Controls.Add(play)

        save = Button()
        save.Text = "save"
        save.Location = Point(10, 70)
        save.Click += self.savePressed
        self.Controls.Add(save)

        self.itemrecorder=Ranorex.Controls.ItemRecorder()
        self.recordtable=self.itemrecorder.CurrentRecordTable
        existing_repo = os.path.join(cwd,'Default.xml')
        if (os.path.exists(existing_repo)
            self.recordtable.Repository = Ranorex.Core.Repository.ElementRepository.CreateFromFile(existing_repo)
        self.recorder = Ranorex.Core.Recorder.Recorder(self, self.recordtable, Ranorex.Core.ElementEngine.Instance.RootElement)
        self.recorder.Start()

    def stopPressed(self, sender, args):
        self.recorder.Stop()
        self.recordtable=self.itemrecorder.CurrentRecordTable
        self.recordtable.RecordedItems.RemoveAt(self.recordtable.RecordedItems.Count-1) #the click on the stop button
        self.recordtable.ReplayEnableTurboMode = True
        self.recordtable.ReplayGenerateReport = False
        System.Windows.Forms.MessageBox.Show('%i'%self.recordtable.RecordedItems.Count)

    def playPressed(self, sender, args):
        self.itemrecorder.PlayAll()

    def savePressed(self, sender, args):
        open('Default.xml','w').write(self.itemrecorder.CurrentRepository.ToXmlString())
        pyrepo=Ranorex.Core.Repository.RepositoryClassGenerator.Generate(self.itemrecorder.CurrentRepository,Ranorex.Core.CodeGenLanguage.IronPython)
        open('Default.py','w').write(pyrepo)
        pyrec=Ranorex.Core.Recorder.RecordTableCodeGenerator.Generate(self.recordtable,Ranorex.Core.Configuration.GlobalDefaults,Ranorex.Core.CodeGenLanguage.IronPython)
        System.Windows.Forms.Clipboard.SetText(pyrec)

form = HelloWorldForm()
Application.Run(form)

We can also use the Repository and RecordTable classes directly. Here is a sample that gets all the absolute paths from a repository.

repo = Ranorex.Core.Repository.ElementRepository.CreateFromFile(existing_repo)
print [str(p.AbsolutePath) for p in repo.AllItems]

You might also like these articles