Use strings to import and run functions from other Sikuli scripts

Asked by CryoGenID

Hello everybody!

I am very impressed by Sikuli so far, and now I would like to use it for a huge testing.
Therefore I have created multiple .sikuli-Files/Directories, one where I have Initialization-Stuff and then one .sikuli-File/Directory
per SubTest.
I have imported the other SubTests fine and can run them with no problem so far in the "Main"-Test-File.
But now I would like to optimize this further:
I would like to realize it that I only have to create a new SubTest (e.g. TestCase23.sikuli in a subdirectory of the "Main"-Test-Sikuli-Directory) and then edit the Main-Test-Sikuli-File and enter the Name of the new TestCase there into the "modulesToRun"-Variable.
The rest should work automatically (that the Main-File calls the new SubTest and runs the tests there).
E.g.:
In the Main-File:
---
modulesToRun = ("InitSystem","Test1", "TestCase23")

tempVar = modulesToRun # Create copy as the for-statement empties the variable
for f in tempVar:
    import f # Import all SubModules

.... # some other code
.... # some other code

tempVar = modulesToRun # Create copy as the for-statement empties the variable
for f in modulesToRun:
    f.RunMe() # Run all the tests per SubModule

---

How can I tell sikuli to treat "f" as an imported modulename for the "RunMe()"-call?

Then I would have a "RunMe()"-function in all SubModules and call all the Tests inside that module from there.

I hope I could explain a little bit, what I am trying to achieve ;-)

I would be greateful for any help!

Thanks a lot and best regards,

Chris

Question information

Language:
English Edit question
Status:
Solved
For:
SikuliX Edit question
Assignee:
No assignee Edit question
Solved by:
CryoGenID
Solved:
Last query:
Last reply:

This question was reopened

Revision history for this message
RaiMan (raimund-hocke) said :
#1

imports cannot be use with string variables.

But why inventing the wheel again?

Have a look at Pythons unittest feature, it has all, that you need.

good start: faq 1804

If you want to stick with your design:
Have a look at Pythons execfile() and Sikuli's addImagePath(). There you could use your strings.

BTW: You say: I would like to use it for a huge testing.
Then you should think about an image repository with a naming convention for your images right from the beginning. So you would not have the images scattered over the different subtests and could further automate the image handling.

Revision history for this message
CryoGenID (cryo-t) said :
#2

Hello Raimund,

thanks a lot for your quick reply.

I will have a look at the unit-test tomorrow!

Regarding the naming convention:
I will also have a look here how to find a good balance between speed in creating the tests and maintaining a useful
naming scheme... But that is why I will divide the huge test into smaller subtests, so that they have their images in their
separate directories...

May I ask one more question:
Do I have a possibilty to tell sikuli to use e.g. Screen(1) for ALL calls which come after this statement?
Currently I am having a variable at each "click", etc. statements which I set in the beginning to the screen I need,
e.g.
screenToUse = Screen(1)
--> all calls in all submodules are like this:
  screenToUse.click(...)

Would be much easier if I could set the system to use a specific screen for "everything" at one single point... ;-)

Thanks a lot and best regards from Munich,

Chris

Revision history for this message
RaiMan (raimund-hocke) said :
#3

Ah gut, mal wieder jemand aus Germany ;-)

--- Screen(1) for ALL calls which come after this statement?

--1. use with region:
saying:
with Screen(1):
    click(some_image)

will search for some_image on Screen(1).
All function calls in the with block are implicitly qualified as Screen(1).function()

If this looks ugly or makes problems (which might be ;-), this is

--2. redefine SCREEN
When a script is started (or first time in the IDE), the a "from sikuli import *" is executed, which in turn makes some initialization.
One aspect is to define SCREEN as Screen(0) and copy all methods to the global dictionary, so that the Screen/Region methods called without a region qualifier are executed as SCREEN.method().

So you might try the following once at the beginning of the main script.

dict = globals()
dict['SCREEN'] = Screen(1)
dict['SCREEN']._exposeAllMethods(__name__)

This is the same code that is used to setup/initialize the SCREEN as Screen(0).

I cannot test it, since I currently have no multi monitor environment.

Revision history for this message
CryoGenID (cryo-t) said :
#4

Hello Raimund,

sorry for the huge delay here, I was completely packed here at work...
But now I want to get this testing working ;-)

Will I have a change to get s.th. like this working to start the other tests:
MAIN-File:
---
import InitSystem
import Test1
import Test2
etc. etc.

modulesToRun = ("Test1","Test2")

if (InitSystem.startUp()==0): # Init the tests
    popup("ERROR during Login, cancelling all tests...")
    exit
else: # Login was OK
    # Testing Target: Full Text Search
    for t in modulesToRun:
        t.runModule()
InitSystem.shutDown() # Shut down the test
---

So each "Test-Module" has a "runModule()"-Function in which I run the individual tests for the Module.
What I only need to do is to the string from the "modulesToRun"-Array to call those "runModule()"-Functions...

How can I realize this? :-)
This would make the testing much more easy as:
- I have a defined point where I have to include/exclude new Test-Modules
- Have an image-directory automatically per Test-Module (and therefore no image chaos)

I hope that you can help me :-)

Thanks a lot and best regards,

Christian

Revision history for this message
RaiMan (raimund-hocke) said :
#5

import InitSystem

modulesToRun = ("Test1","Test2")

if (InitSystem.startUp()==0): # Init the tests
    popup("ERROR during Login, cancelling all tests...")
    exit
else: # Login was OK
    # Testing Target: Full Text Search
    for t in modulesToRun:
        exec("import "+ t)
        exec(t + ".runModule()")

InitSystem.shutDown() # Shut down the test

But why inventing the wheel again? Why do you not use the mighty unittest module?

Revision history for this message
CryoGenID (cryo-t) said :
#6

Hello Raimund,

thanks a lot for your reply.

I have now streamlined the code a little bit so that it is easier to read:
---
import InitSystem

modulesToRun = ("Test1", "Test2")

popup("OK starting Tests in Modules")
for currentModule in modulesToRun:
    popup("Working with module " + currentModule)
    exec("import "+ currentModule)
    exec(currentModule + ".runModule()")
print("Testing ended.")
---
This is the content of Test1 and Test2:
---
from sikuli import *

def runModule():
    popup("Run Test2")
---

What I am now getting if I run the main file is:
---
[error] Fehlermeldung: Traceback (most recent call last):
 File "c:\temp\windows\sikuli-tmp2249617525542428429.py", line 21, in
 exec("Test1"+".runModule()")
File "", line 1, in
AttributeError: 'module' object has no attribute 'runModule'
---

Why doesn't it find the function?
Do have an idea for me?

Thanks a lot and best regards,

Christian

Revision history for this message
RaiMan (raimund-hocke) said :
#7

Having these .sikuli in one directory:

# main.sikuli
modulesToRun = ("Test1", "Test2")
print("OK starting Tests in Modules")
for currentModule in modulesToRun:
   print("Working with module " + currentModule)
   exec("import "+ currentModule)
   exec(currentModule + ".runModule()")
print("Testing ended.")

# Test1.sikuli
from sikuli import *
def runModule():
    print "from Test1"

# Test2.sikuli
from sikuli import *
def runModule():
    print "from Test2"

the following output is produced when running main:

OK starting Tests in Modules
Working with module Test1
from Test1
Working with module Test2
from Test2
Testing ended.

Idea: there are problems with Sikuli's import with path names like this:

path-workfolder/main.sikuli
path-workfolder/Test1/Test1.sikuli
path-workfolder/Test2/Test2.sikuli

so don't do that.

If you need a folder structure:

path-workfolder/main.sikuli
path-workfolder/Tests/Test1.sikuli
path-workfolder/Tests/Test2.sikuli

and add in main:

import os
dir = os.path.dirname(getBundlePath())
tests = os.path.join(dir, "Tests")
if not tests in sys.path: sys.path.append(tests)

Revision history for this message
CryoGenID (cryo-t) said :
#8

Hello Raimund,

sorry that this takes forever :-(

I still get the error... I have stripped my test down to the skeleton and uploaded it here, perhaps
you have a few minutes to try it out on your side if it is just my PC here...
Here is the link:
http://zwischenspeicher.net/cloud/apps/files_sharing/get.php?token=ea9a8f81602160803e3c5767aba20fc8f9ce65e2

Thanks a lot for your help! :-)

Best regards

Christian

Revision history for this message
RaiMan (raimund-hocke) said :
#9

Sorry, that might have been my fault: my primary systems are Macs, so I always check on these first.
In this case, I only now went on my Win7 to test ...
and UUUUuuups, it does not work as expected.
This is based on a bug, with internally acting on sys.path creating odd entries, that I thought would have been fixed with the latest build r930/931 - but it seems that not fixed.

So I have made a script, that is equivalent, but ugly - but it works.
I go down to the .py level for the import and so I have to add the image path.

So sys.path and image path are prepared before the import/run and reset afterwards (looks a bit weird, but this stuff is really not yet stable in Sikuli).

The debug prints can be deleted, when it works.

this is the code:

import os
dir = os.path.dirname(getBundlePath())
tests = os.path.join(dir, "Tests")

modulesToRun = ("Test1", "Test2")

popup("OK starting Tests in Modules")
for currentModule in modulesToRun:
    popup("Working with module " + currentModule)

    currDir = os.path.join(tests, currentModule+".sikuli")
    if not currDir in sys.path:
        sys.path.append(currDir)
        addImagePath(currDir)

    # debug only
    print("Working with module " + currentModule)
    for e in sys.path: print e
    print "1 IPATH*** ", list(getImagePath())
    # debug only

    exec("import "+ currentModule)
    exec(currentModule + ".runModule()")

    # debug only
    for in in range(3): sys.path.pop()
    for e in getImagePath(): removeImagePath(getImagePath().pop())
    print "2 IPATH*** ", list(getImagePath())
    # debug only

print("Testing ended")

Revision history for this message
RaiMan (raimund-hocke) said :
#10

uups, the last debug only block:

    for in in range(3): sys.path.pop()
    for e in getImagePath(): removeImagePath(getImagePath().pop())

    # debug only
    print "2 IPATH*** ", list(getImagePath())
    # debug only

the 2 for loops are the path reset actions!

Revision history for this message
CryoGenID (cryo-t) said :
#11

Raimund,

thanks a lot, that seems to work :-)
I will now try to incorporate that into my other project...
The only thing that is not working is when the files are all on a mapped network share, e.g. Z:\
---
Fehlermeldung: SyntaxError: ('mismatched character \'\\n\' expecting \'"\'', ('', 1, 17, 'addModPath("Z:\\")\n'))
---
That seems to happen already in line 1 ("import os")...

When I use the UNC to access the share "\\server\drive\directory" it is working, so that might perhaps be a bug...

As it is working using the UNC it is not that important, but perhaps it can be fixed for some later version ;-)

But again THANKS A LOT Raimund!

My next task is to get the system working so that a test can run either on one external TFT or the other...
(I might get back to you on that if I may *g*)

Best regards,

Chris

Revision history for this message
CryoGenID (cryo-t) said :
#12

Thanks RaiMan, that solved my question.

Revision history for this message
RaiMan (raimund-hocke) said :
#13

--- thanks for the feedback
especially about the workaround for mapped network shares.

Yes, this is the (known) buggy situation with the ImagePath handling on Windows.

--- always welcome ;-)

Revision history for this message
CryoGenID (cryo-t) said :
#14

Raimund,

sorry for the long silence in this thread...
Work was really busy but now we starting with the programming of the testing.

And we are struggling with the multimonitor topic...
Following your last post regarding this topic:
---
--2. redefine SCREEN
When a script is started (or first time in the IDE), the a "from sikuli import *" is executed, which in turn makes some initialization.
One aspect is to define SCREEN as Screen(0) and copy all methods to the global dictionary, so that the Screen/Region methods called without a region qualifier are executed as SCREEN.method().

So you might try the following once at the beginning of the main script.

dict = globals()
dict['SCREEN'] = Screen(1)
dict['SCREEN']._exposeAllMethods(__name__)

This is the same code that is used to setup/initialize the SCREEN as Screen(0).

I cannot test it, since I currently have no multi monitor environment.
---

I have inserted the code from above, but regardless which number I use for Screen(X), Sikuli always uses the screen
which is defined as the "main" screen in Windows :-(

Any further ideas how to get this working?

Thanks a lot and best regards,

Christian

Revision history for this message
RaiMan (raimund-hocke) said :
#15

Uuups, SCREEN has to be redefined also, sorry ;-)

so complete now:

SCREEN = Screen(1)
dict = globals()
dict['SCREEN'] = SCREEN
dict['SCREEN']._exposeAllMethods(__name__)

Revision history for this message
RaiMan (raimund-hocke) said :
#16

BTW: tested on my Mac Lion with 2 monitors

Revision history for this message
CryoGenID (cryo-t) said :
#17

Hello Raimund,

thanks for your eMail.
Well the problem is: I have three screens.
If I run the script with Screen(0) or Screen(1) or Screen(2) it runs, but always uses the default Monitor of Windows.
(I have the program which should be controlled by Sikuli open on all three Monitors)

If I enter Screen(3) then I get an error, which means that Sikuli knows that I have three Monitors.

Does Sikuli save somewhere on which screen the action was originally recorded and always uses that screen?

What I want to achieve is that Sikuli runs Test A on Screen 1, then on Screen 2. Then Test B on Screen 1, then on Screen 2, etc. ;-)

Thanks a lot for your help :-)

Best regards,

Christian

Revision history for this message
CryoGenID (cryo-t) said :
#18

Update: If you have a few minutes, we could also do a quick screensharing and you could try it out youself if you want to :-)

Revision history for this message
RaiMan (raimund-hocke) said :
#19

Does the approach from comment #15 principally work on your Windows environment?

some quick test like this:

SCREEN = Screen(1)
dict = globals()
dict['SCREEN'] = SCREEN
dict['SCREEN']._exposeAllMethods(__name__)
hover(getCenter())

should move the mouse to the center of Screen(1)

BTW: this should report your screens you have and there relative position as recognized by Sikuli:

for i in range(getNumberScreens()):
    print Screen(i)

Revision history for this message
RaiMan (raimund-hocke) said :
#20

Before proceeding, pls. confirm #19

Revision history for this message
CryoGenID (cryo-t) said :
#21

Hello Raimund!

That worked perfectly, the mouse moved to each screen into the middle...

So the question is: Why doesn't it work with multiple files?
I am afraid the the setting is not taken into account when the "main"-file runs the sub-files (see #9 / #10)...
I will try to insert that statement into those files also and report back :-)

Thanks a lot already!

Best regards,

Chris

Revision history for this message
RaiMan (raimund-hocke) said :
#22

yes, I guess, that as with

from sikuli import *

you have to put the sequence just after this statement in every sub.

You might use
Settings.myScreen = 1 # or 2

to gloabally set the Screen and in the subs use

SCREEN = Screen(Settings.myScreen)
dict = globals()
dict['SCREEN'] = SCREEN
dict['SCREEN']._exposeAllMethods(__name__)

To select the currently to be use Screen.

Settings is a globally available static Sikuli class.

Revision history for this message
CryoGenID (cryo-t) said :
#23

Hello Raimund,

that seems to work :)

Now I am fighting again with the subtests (they cannot be opened, but I am on that *g*)...

Thanks A LOT (!) for your help!

Best regards,

Chris