Can Sikuli Methods be Overridden?

Asked by G-Mint on 2012-05-15

Hi.

I'm trying to find out if the custom methods in siluli, e.g. click(), can be overridden using jython? I ask as I'm looking to add additional features to the methods at the API level. Such as measuring the time it takes for the button to be found and clicked each time.

Any help would be brilliant.

Thanks,
Grant.

Question information

Language:
English Edit question
Status:
Answered
For:
Sikuli Edit question
Assignee:
No assignee Edit question
Last query:
2012-05-15
Last reply:
2015-07-20
j (j-the-k) said : #1

I would suggest something like this:

import sikuli.Sikuli as sikuli # import sikuli manually to get access to the original methods

def click(img):
    # do custom stuff
    sikuli.click(img) # execute sikuli original method

click("img.png") # should now execute custom click command

RaiMan (raimund-hocke) said : #2

@ (j-the-k)
principally ok, but the sikuli.Sikuli does not contain the click() and other Region methods.
They are either in sikuli.Region or in the Java Region class.
And most of the methods are instance methods and not class methods.

So your click() suggestion will only work for unqualified usages, which search on the whole screen.

reg.click() will still use the Region method.

*** Depending on what you want to achieve (e.g. some wrapping), you have to use something like:

class myRegion (Region):

     def __init__(self, reg):
          self.theReg = reg

     def click(self):
           # some code
           self.theReg.click()
           # some code

usage e.g.
reg = find(something)
myReg(reg).click()

This would help in the mentioned situation (track timing).

The problem in Python/Jython is, that you might well subclass, but not selectively override on the method level (as is possible e.g. in JRuby) (with the normal language features).
There are other possibilities, if you step down to the Java level (which can be done from the Sikuli script level).

*** Another possibility for the time tracking issue:

def xt(cmd):
    # timer start
    ret = eval(cmd)
    # timer end

usage e.g.
xt('click(image)')

j (j-the-k) said : #3

Okay I dind't think about region.click(), you're right RaiMan.

Ryan (ryan-pisano) said : #4

I too am hoping for something similar. In my project we are extending the Region class extensively and sometimes need click to have a side effect on the derived class.

For example:

class checkBox(parentClass): #parentClass is a hierarchy of super classes that implement different domain-specific actions
 # Parent class eventually inherits from Region and initializes the actual region

    def __init__(self, region, isSelected):
        super(checkBox, self).__init__(region) # The super class passes the actual region bounds all of the way up to Region()
        self.isSelected = isSelected

    def click(self):
        super(checkBox, self).click()
        self.isSelected = not self.isSelected

This way I can always do the following:

component = checkBox(regionInfo)
component.click()

I like to keep my elements polymorphic to save lots of code across the automation and references can be passed around based on what is actually found on the screen. Without trying to override click() I am able to use component.click() to click in the center of the component's region.

I get a recursion error when I use the code shown.

When I try to use parentClass.click() within the click() method I get: TypeError: click(): expected 1-3 args; got 0

Based on this exception it sounds like I really just need to provide some kind of region/instance so I tried to pass self in since it is a region, and even strip the region from the class so that I'm just passing a region: UIBaseClass.click(self)

Though this is a single argument I get: TypeError: click() takes exactly 1 argument (3 given)

Is there something that can be done to provide the correct argument?

RaiMan (raimund-hocke) said : #5

@Ryan
Inheriting from the class Region on the Python level is problematic.
This has historical reasons and is due to the fact, that on the scripting level on can say:

click(someThing)

instead of
someRegion.click(someThing)

this is a convenience, that binds these undotted method usages to the "constant" object SCREEN at runTime, which is Screen(0).

If you want to do a robust inheritance, then this way:

import org.sikuli.script.Region as JavaRegion

class myComponent(JavaRegion):
    pass

this way you are directly using the Java class Region, where all the methods are implemented.

BTW: I generally do not recommend, to use inheritance, but to implement some wrapper class for your own features.
This way, you have more freedom, to switch between the 2 worlds (yours and SikuliX).

Ryan (ryan-pisano) said : #6

One of the reasons we did this was to intentionally block the global undotted methods. The way our testing and framework are set up already removes the ability to run modules as scripts, but further, the flexibility needed requires a structure similar to the page-object model. We create a hierarchy of regions (ex: Screen > Browser > Applications> header, footer, navigation, page > selector frame > checkbox) Each region only knows the region given to it and what components to look for within itself. This means every component uses self.xyz to manipulate the region.

Using the Java region still gives me the same issues, so I think I'm going to fore-go the polymorphism and just implement a switchCheckbox(status) method that utilizes click.

I did initially have every class contain a region attribute containing the sikuli region, however as I started organizing some of our types things started getting wordy and in some cases did not make sense. Up until now when I wanted to store the status of the checkbox I hadn't noticed any issues with the inheritance, which is why I assumed it was possible.

Thanks!!

Can you help with this problem?

Provide an answer of your own, or ask G-Mint for more information if necessary.

To post a message you must log in.