Using common function defs in other scripts

Asked by Ben Turner

I've viewed quite a few articles around this subject, the primary one being https://answers.launchpad.net/sikuli/+question/102003, and others about using "include" calls, which knowing nothing of Python is a bit beyond me right now.

However, from the primary article, I have as always tried a simple example, and created the following files and directories :

+-- lib.sikuli (contains lib.py and button.png)
def pressButton():
    click("button.png")
---

+-- script.sikuli (contains script.py)
execfile("C;\\path-to-tests\\lib.sikuli\\lib.py")
pressButton()
---

However, upon trying to execute script.py, I am getting the following exception :

      edu.mit.csail.uid.FindFailed: FindFailed: button.png can't be found

I assume this is because script.py is invoking the pressButton command, and looking for button.png in the script.sikuli directory, and not the lib.sikuli directory. So is there a simple way of adding this directory into the "search path" or whatever concept Python / Sikuli uses to find the images ?

As an aside, I did try some of the more involved "include" function examples, but I am trying to write unit test scripts at the moment, and was having trouble with these concepts. I am hoping, once I have got this working, I can simply put the execfile command into a setUp function, and then these library functions (along with requried images) can be available in all the test methods. Not sure if any of this might change any advice offered.

Many thanks for your assistance,
Ben

Question information

Language:
English Edit question
Status:
Solved
For:
SikuliX Edit question
Assignee:
No assignee Edit question
Solved by:
RaiMan
Solved:
Last query:
Last reply:
Revision history for this message
Best RaiMan (raimund-hocke) said :
#1

in the current release, you have to decide, which battle you want to fight:
1. have all pictures in one place (one .sikuli) (easier in execution, more complex in creation)
2. using set/getBundlePath() to tell Sikuli whenever needed, where your images are (may result in many calls to these functions)
3. in the called def()'s qualifiy the images with the absolute path

Taking into account, that the next Sikuli X (not yet dated), will have a flexible import feature together with transparent handling of image-locations, I would recommend to use 3. for someone who just starts with the idea of reusing things.

How To:
- have all your .sikuli's in one directory (not really necessary, but easier)
- have all your called def()'s in one .sikuli

I will come back later.

Revision history for this message
Ben Turner (ben-turner) said :
#2

Have put "..\lib.sikuli\1234567890.png" in for image references as advised, which ensures these work for both the library itself and all imports.

Obviously care needs to be taken when editing these values via the IDE, as upon saving, it will reset all images to the filename only. Will write a script to auto-prefix the image names again if this becomes a problem. Hopefully such shared function libraries are pretty static though, so won't need to much alteration once they are ready.

Thanks for the rapid help again - very awesome.

Revision history for this message
Ben Turner (ben-turner) said :
#3

Thanks RaiMan, that solved my question.

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

Sorry, to have left you alone - but had some other priorities ;-)

Congratulations: you have found the solution.

Tip: if you use e.g.
dirImg = "..\lib.sikuli\" # at the beginning of your script, it will be global to all def()'s included with execfile()

and later e.g.:
click(dirImg+"image-name.png")

you will not have the save problem and see the thumbnails when you edit your lib script.

so later on when Sikuli X is working, you only need to set
dir = ""

I have solved the thing for me in a more general way:

in the def():
imgAppSectionSubsectionFunction = dirImg+"captured-image.png"

based on a naming convention I reference the images by using their variable name:
click(imgAppSectionSubsectionFunction)

This is more useful with the normal Python import
from imagelibrary import *

I have evolved the handling of reusability to use import instead of execfile(). If interested, come back.

Revision history for this message
Ben Turner (ben-turner) said :
#5

To get this working with the unit testing framework, I had to do the following :

def setUp(self):
    self.dirImg = "..\\path\\"

def testStuff(self):
    click (self.dirImg + "image.png")

e.g. just setting these values in the script outside of all the def's failed

Perhaps as I get into Python more, I will understand why that is (certainly I can get it to work without setting these values on "self" outside of the unit testing setup)

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

OK, you found a solution, that is fully ok.

Some Information to the background:

- Internally, when pressing run on the Unit Test page, a unit test class named with the name of your script (therefor, it has to be saved before being able to testrun).
e.g. if you save to myTest.sikuli, the generated class name is myTest.

- during testrun, self represents the current instance of the testrun, which is of class myTest

- all variable assignments and code outside of any def's are run once during creation of the instance and can only be referenced on the class level.
e.g. you say:
dir = "some-path" # outside of the def()'s

later on you can use it with myTest.dir (since dir is a class variable it has to be referenced with the class name)

- everything that is referenced inside a def() is local to the def. If you need something in other def()'s too, you have to either create it on the instance level (self.anything) or on the class level (e.g. myTest.anything) and use it accordingly.

In Sikuli testrun, the place to create or do something on the instance level once per testrun is the def setUp(self):, which plays the role for test classes as __init__ for normal classes (create and set instance variables).

Since with every testrun, the class and the instance are created from scratch, this is sufficient. Having something on the class level, would not have any additional benefit (there will never be another instance in the same run) and on top needs to be edited, when the script file name is changed (it makes the class name).

There is still a "trick", if you do not want to qualify your "I-want-to-use-them-in-all-defs"-variables with self.:
global someValue
someValue = "some-text"

def test1(self)
   popup(someValue)

should work.