Friday, August 23, 2013

Detecting Notification messages that fade away using JavaScript

There's a lot of legacy code I have to automate that was designed before my time.  With it comes many annoyances like fancy fade-in-fade-out notification messages that doesn't leave a trace that they ever appeared.  On top of that, I'm currently running my tests on a 3rd party grid where the time it takes for a selenium command round trip can be much slower than how long the message stays on the screen.

Here's an example: http://screencast.com/t/1PrhUrDSMG

In that 1 second that message is on the screen.  2 round trip Selenium commands to first check if the message is on the screen, then to get the message text can take longer than a second when using a 3rd party selenium grid provider that's on an outside network (like Sauce Labs or Browserstack).

Doing it before using a Selenium polling loop.

Before I had a simple polling loop that checked for this error message.

end_time = datetime.now() + timedelta(seconds=10)

while datetime.now() < end_time:

    err_msg = self.webdriver.find_element_by_id("message")
    if err_msg.is_displayed():
        # This could be subjected to stale element errors 
        # if element disappears between the 2 selenium calls
        # needed to check if the element is visible, then to 
        # get the text.
        text = err_msg.text  
        if text == 'Success':
            break
        else:
            raise PageError(text)
    
    # needed a sleep to prevent flooding the network 
    # connection to SeleniumGrid with traffic, however, 
    # the element can disappear during this time.
    time.sleep(0.5) 

It had many issues like the element disappearing off the screen before it could be found, or the element properties changing before I can read the text causing a StaleElementReferenceException. Since sending commands using selenium is very slow, it's hard to do polling on element visibility or content for element, because the properties change and disappear off the screen during the period the Selenium commands were still in transit or during our sleep interval.

Pushing the polling for success / error messages to the browser side.

 To solve this issue, we can have a faster polling for that disappearing message element that's entirely in JavaScript.

Injecting a JavaScript Interval for fast polling.

First we create a method that'll inject a javascript polling interval.
def inject_javascript_polling_latch(driver)
    js_command = """
 window.__TEST_message_listener_latch = false;
 window.__TEST_message_listener = window.setInterval(function(){
  if (document.getElementById("message").style.opacity > 0) {
   window.__TEST_message_listener_latch = true;
   window.clearTimeout(window.__TEST_message_listener);
  }
 },100);"""
 
    driver.execute_script(js_command)
This will first clear a latch variable.  Then create a polling interval that'll fire off every 100ms, which is a lot faster than if we polled using selenium.  What this does is when the message appears on screen (in this case marked by the opacity going higher than 0 from the fade in effect, it'll automatically set the latch to True.

Checking the latch is set

After the latch is injected, I can simply check if the latch is set on my wait loop, which is running slower because it has to wait on a round trip from the machine running the test and the machine running the browser.

wait_condition = lambda: self.webdriver.execute_script("return window.__TEST_message_listener_latch_message") == True
wait_until(wait_condition)

And there you have it.  A simple way to shift the polling loop from the test machine that has to deal with round trip delays, to the browser that'll execute against the page itself with no delays.



9 comments:

Unknown said...

same thing is happening in our application.But i am writing tests in c#. Your code is in python.Please help me with c# code.

Unknown said...

Hi david Lai,

Now i am able to check the notification message.But i am not able to get text from that element.The element is inside a span tag .same span tag shows 3 different messages while add,update,delete. I tried to assert text in span while adding but i am not able to get text from that. Help me regarding this.

David Lai said...

You can also use the technique to save message to a JavaScript variable.

You can use the innerText or textContent property (depending on which browser), and save that to another JavaScript global, alongside the JS latch.

Unknown said...

I am able to get text now. I achieved that with default wait property wit polling intervals set.
But I am also curious to get the javascript code you have provided in C#.

David Lai said...

I'm not familair with the webdriver c# syntax.

But everything in JavaScript should stay the same, but may have differences depending on which browser you use.

The idea is you can craft JS statements to save values into global variables. Then you can use executeScript to return those back when you poll on them.

Unknown said...

Thanks David. I will try

Unknown said...

i got it now.used jquery in this context.without using latch, i am able to get inner html of the notification message.Thanks for make me think in different approach.

Unknown said...

hi,
could u please help me in the polling code in c#...i'm having the same issue of fading the text message before it is located.

David Lai said...

You should be able to use the JS code verbatim since that is injected. Only diff between Python and C# in this case is how you define your method signatures.