Forums

Selenium RC / Python / JavaScript Inquiry

Hello folks, I have been teaching myself Python over the last few months. My first project is a browser driver test case using the Selenium RC web driving framework (Not importing webdriver in my project though, so as not to confuse). Basically, my test case needs to hook into a refresh loop of a '.aspx' page that is written in JavaScript and HTML. This loop constantly refreshes this '.aspx' page until a 'sel.is_text_present("foo")' condition is satisfied. After refreshing until the defined 'text' appears the script is supposed to 'click' a JavaScript button that appears along with the 'is_text_present' string. Upon executing the script Firefox opens and directs itself to the specific page and enters its 'refresh loop', but when the text appears (i.e. the '.aspx' page changes its state and loads a JavaScript table with a JavaScript button) the script doesn't execute the other branch of the loop. Here is a snippet of code which shows the logic I have implemented:

i = sel.is_text_present("Schedule")    #text that the test case is 'waiting' for
    while i != True:
        print i
        print "Waiting for job..."
        sel.refresh()
    else:                
        print "Job found..."
        sel.click("id=Select")
        sel.wait_for_page_to_load("30000")
        sel.click("id=1")
        sel.wait_for_page_to_load("30000")
        print "Success!"
        test_py_s_f_t_c()    #calls this function again to repeat the test case

Any suggestions, or 'pointings' in the right direction would be most helpful and appreciated! Looking forward to getting acquainted with this community! ^_^

Your code as posted never changes the value of i -- it's set once at the top of the loop, and then isn't updated after you've done the refresh. I think you want something like this:

text_found = False
while not text_found:
    text_found = sel.is_text_present("Schedule")    #text that the test case is 'waiting' for
    print text_found
    print "Waiting for job..."
    sel.refresh()

if text_found:                
    print "Job found..."
    sel.click("id=Select")
    sel.wait_for_page_to_load("30000")
    sel.click("id=1")
    sel.wait_for_page_to_load("30000")
    print "Success!"
    test_py_s_f_t_c()    #calls this function again to repeat the test case

Shouldn't 'text_found' initially be set to 'True' rather than 'False'? In your example I read the while loop as, 'while text_found == true:'. Because, 'not false' is Boolean logic for 'true'. The 'is_text_present()' function is returning a false until it finds 'Schedule' present somewhere as text in an element. If 'text_found' is set to 'False' and the 'while not' loop is looping on a 'True' and the 'is_text_present()' is returning a 'False', then when the 'is_text_present()' detects the word 'Schedule' and changes 'text_found' to 'True' what will happen?

The condition in the loop that Giles presented looks correct to me - however, I did spot a potential issue...

Since text_found starts as False, the loop condition not text_found evaluates to True (because not negates its operand, and the negation of False is True). This means that the while loop executes at least the first time.

Thereafter, all the while the text is not present, the value of text_found remains False, hence the result of not text_found remains True, hence the loop continues to execute. As soon as the text is found text_found is set to True and the next time around the loop it will terminate.

The thing I'm uncertain about is whether then calling sel.refresh() again is going to block incorrectly - this is because I'm not at all familiar with Selenium. If it's incorrect to call sel.refresh() again then as far as I can tell you can simplify the loop down to:

while not sel.is_text_present("Schedule"):
    print "Waiting for job..."
    sel.refresh()

This is assuming that there doesn't need to be an initial call to sel.refresh() for this to work - again, I'm not familiar enough with Selenium to say for sure.

Actually, having thought about it a little more, I think the real problem with the code I suggested is that it won't terminate if the text never appears -- that should cause some kind of test failure. So it should probably read (assuming that you want it to terminate if the text isn't present after 10 retries):

text_found = False
retries = 0
while not text_found and retries < 10:
    text_found = sel.is_text_present("Schedule")    #text that the test case is 'waiting' for
    print text_found
    print "Waiting for job..."
    sel.refresh()
    retries += 1

if text_found:                
    print "Job found..."
    sel.click("id=Select")
    sel.wait_for_page_to_load("30000")
    sel.click("id=1")
    sel.wait_for_page_to_load("30000")
    print "Success!"
    test_py_s_f_t_c()    #calls this function again to repeat the test case
else:
    # Do something to signal that the test has fails.

Okay, great folks this really helps. I appreciate all the input and I understand this better now. My only question remaining is, "Why does the while loop have to be followed by a 'if' statement in giles' example?" Thanks again! p.s. Cartroo, I am not sure about your point either, but I will be sure to keep it in the back of my mind as I go along.

In the first example as written, the if isn't strictly required as the first loop will only exit once text_found is True. However, it's often a good idea to make your code robust against possible changes you might make in the future, although of course there's a limit to how much you can usefully do this.

To illustrate this, Giles' latest example adds a maximum retry count, and now the if is very much required to handle the case that the loop exits due to a maximum retry count rather than finding the text.

Personally I might be inclined to also add a short delay around the loop (e.g. time.sleep(0.2)) but that depends on whether sel.refresh() already incorporates a delay or blocking wait of some kind. I wouldn't want to think the result of a test was dependent on the speed of machine on which it was running and it's not clear how quickly a fast machine could do 10 retries. The other option would be to make it a time limit as opposed to a retry count, but doing refreshes in a tight loop like that might be quite CPU-hungry if the refresh is very fast.

@Cartroo -- you're right, as always. Normally with a refresh I'd assume that the test is OK to do a busy wait like that, because refreshes are network-bound so you're unlikely to max out the CPU. But if it was any other kind of test (say, checking if some JavaScript had changed the page, so a refresh wasn't required) then I'd definitely put a one-second sleep or so.

Sure, the IO-bound nature of the test means it's probably safe - but it occurred to me that it might not be uncommon to be running such a test on the same host as the server, where the network delay would be considerably lower.

Righto, thank you for the insight into hypothetical considerations concerning testing. The test is now functioning almost as required. The decision making in the loop is dead on, but I do have a possible scoping issue with calling the function over again:

class pySFTC(unittest.TestCase):
def setUp(self):
    self.verificationErrors = []
    self.selenium = selenium("localhost", 4444, "*firefox", "http://www.myurl.com/")
    self.selenium.start()

def test_py_s_f_t_c(self):
    sel = self.selenium

    sel.open("http://myurl.com/")
    sel.type("UserName", "myUN")
    sel.type("Password", "myPW")
    sel.click("id=Submit")
    sel.wait_for_page_to_load("30000")
    sel.set_timeout("")
    sel.click("css=div.NavText")
    sel.wait_for_page_to_load("30000")
    sel.wait_for_page_to_load("")
    sel.open("/myResource.aspx")
    text_found = False    #text that the test case is 'waiting' for
    while not text_found:
        text_found = sel.is_text_present("Schedule")
        print text_found
        print "Waiting for job..."
        sel.refresh()
        time.sleep(1)
        sel.wait_for_page_to_load("30000")

    if text_found:                
        print "Job found..."
        sel.click("id=Select")
        sel.wait_for_page_to_load("30000")
        sel.click("id=1")
        sel.wait_for_page_to_load("30000")
        print "Success!"
        f = open("sel_log.txt", "a")
        f.write("A job was found at: ")
        f.write(datetime.datetime.now().ctime())
        f.write("\n")
        f.write("\n")
        self.test_py_s_f_t_c()     #calls this function again to repeat the test case

When I had the final line as, 'test_py_s_f_t_c()' I received the error: 'NameError: global name 'test_py_s_f_t_c()' is not defined', so I revised it to 'self.test_py_s_f_t_c()', but I am receiving a new error: 'Exception: ERROR: Element UserName not found'. Maybe you folks are aware of a better way of repeating, i.e. calling, the function again! Thanks.

Calling a function from within itself is quite valid, it's a technique known as recursion. However, I don't think it's what you want to do here.

Whenever you call a function from inside itself, you end up with an extra stack frame (an extra "layer" of function calling) which takes up some extra memory. If you do this too deeply, you'd run out of memory eventually. To prevent you doing this, Python imposes an arbitrary limit of 1000 recursive calls - if you try to go any deeper than this, it raises an exception which (unless caught) will terminate your program.

Because your function calls itself as its very last case, it's a special case of recursion called tail recursion. Some compiled languages can optimise this to avoid the extra stack frame, but Python doesn't attempt to do this so you still need to avoid the recursive call.

Fortunately, the solution in this case is quite easy. Instead of the function calling itself, just use an additional while loop:

class pySFTC(unittest.TestCase):

    # ... code omitted for brevity...

    def test_py_s_f_t_c(self):
        sel = self.selenium
        while True:
            sel.open("http://myurl.com/")
            # ... code omitted for brevity...
            while not text_found:
                text_found = sel.is_text_present("Schedule")
                # ...   code omitted for brevity...
            if text_found:                
                print "Job found..."
                # ... code omitted for brevity...
            else:
                break

You can see that the whole thing is run in an infinite loop (while True will loop forever) but if text_found is not true then the loop exits with the break statement. You could write this loop different ways if you wanted to, such as while text_found and setting text_found = True before the loop starts, it's all just a matter of taste. The key point is using the while loop instead of calling the function again.

As an aside, I've glossed over a few technical details in this post, but they're not important to the points I'm making.

I cannot explain how grateful I am for this information. Let me know if I am asking questions in an annoying/obnoxious manner. Well I inserted the while True: loop to re-continue this function, however I received the same error as I did when I used the tail recursion method. I am thinking this is something specific to do with Selenium and the setUp(self) and tearDown(self) functions; i.e. it may be the case that these functions have to do some type of procedure in order for it to work properly. Thanks so much for all your help--truly a wonderful community here!

Edit: I did some more thinking and came to realize that Selenium cannot find the HTML Element for typing in your 'Username' b/c the cookies have been cached with this session's browser profile; i.e. you have already logged in and hence are logged in. I will put the while True loop lower in the code's sequence and this should solve it.Thanks again!