Highlighting the element before any clicks: A foray into the AbstractEventListener

Last week I wrote a post about how you can create a highlight method to help you visually find an element that you have selected. In the comments section, wiiniRyan suggested implementing the highlight method before all clicks occur using one of the Abstract Event Listeners hooks provided in the Selenium::WebDriver::Support module. Only thing was, he couldn’t figure out how to use the hooks according to his comments, and suggested that I might be able to provide some insight.

His comments intrigued me, so I did a little bit of research and hands on testing to figure out how these hooks work.

I looked at the documentation, but didn’t see anything real obvious about how to hook into them. My first attempts simply tried to subclass the AbstractEventListeners class, but that did no good, since the driver had no way to interface with the overridden hooks.

It wasn’t until I read a small crucial line after the #for method call on the main WebDriver documentation page that I figured out how these hooks worked:

One special argument is not passed on to the bridges, :listener. You can pass a listener for this option to get notified of WebDriver events. The passed object must respond to #call or implement the methods from AbstractEventListener

Essentially, you need to create a class with either every method hook defined, or use the generic #call method to delegate the hooks as they occur.

# Either
class AbstractDefineEveryMethodTest
  def before_click *args
    # .. do stuff
  end
  def before_#... rest of the hooks defined
end
# The downside to the above is that every single method
# hook needs to be defined, or else things will blow up
# when the hook tries to be called.
# Or, more efficiently
class AbstractHandleCallMethodTest
  def call *args
    # only need to deal with the hooks you are interested in
    case args.first
    when :before_click
      puts "Clicking #{args[1]}"
      # .. do stuff
    when :before_find
      puts "Finding #{args[1]} => #{args[2]}"
      # .. do other stuff
    end
  end
end

After creating this class, then we just create a new instance of the class and pass it into the initialization of our WebDriver browser:

ael = AbstractHandleCallMethodTest.new
driver = Selenium::WebDriver.for :firefox, :listener => ael

So, now when you find an element and click it, you will see the following output:

driver.find_element(:css => "h1").click # =>
Finding css selector => h1
Clicking #<Selenium::WebDriver::Element:0x000000022f6528>
=> "ok"

So, its a simple matter of adding the highlight method to the AbstractEventListener and using the #call method to call highlight when we want to.

class HighlightAbstractTest
  def highlight element, driver
    orig_style = element.attribute("style")
    driver.execute_script("arguments[0].setAttribute(arguments[1], arguments[2])", element, "style", "border: 2px solid yellow; color: yellow;")
    sleep 1
    driver.execute_script("arguments[0].setAttribute(arguments[1], arguments[2])", element, "style", orig_style)
  end
  def call *args
    case args.first
    when :before_click
      highlight args[1], args[2]
    end
  end
end
hat = HighlightAbstractTest.new
driver = Selenium::WebDriver.for :firefox, :listener => hat
driver.find_element(:css => "h1").click # => Be sure to watch in your browser window for the highlight to appear!
driver.find_elements(:css => "input[type='button']").each{|b| b.click } # => Be sure to watch in your browser window for the highlight to appear!

A couple words of caution:

First, if you are going to define each hook instead of using the recommenended #call method, be sure you define each and every hook inside your class. Otherwise, if that hook attempts to be executed, you will get a nasty error message about not being able to find the method.

Second, not all of the hooks provide the same parameters. In fact, the only hooks to include the element and driver, which are needed for the highlight method to work are the before_click, before_change_value_of (still trying to figure out when that one is triggered), after_click, and after_change_value_of. The other hooks have different parameters according to their respective function, which can be found by RTFM. Don’t get burned when you expect before_find to pass the whole element as a parameter. And I wouldn’t recommend trying to get the actual element inside this hook with the by and what parameters, lest you create an infinite loop.

AbstractEventListener seems like an interesting idea, but is a little bit complicated to set up. Also, I am not sure what other use cases might come from implementing these hooks. If you have some ideas, please share them in the comments!

Advertisements

12 thoughts on “Highlighting the element before any clicks: A foray into the AbstractEventListener

  1. Awesome! It’s that crucial :listener line that I missed as well – Thanks a lot.

    As to your first note of caution, I’d recommend just making your event listener inherit Selenium::WebDriver::Support::AbstractEventListeners, and then overwrite the methods you’d like to use, as necessary.

    As for use cases: I wrote an adapter layer on the Webdriver because I wanted some custom functionality, but didn’t want to rely on watir-webdriver (a little too verbose for me). These hooks provide me the functionality to implement before and after calls, without modifying all of my adapter methods (clicking, typing, etc).

    And who knows what else we could use it for. It’d be an excellent debugger, it could easily generate documentation… the possibilities are endless πŸ™‚

    • Good thought on the inheritence.

      Yeah, I didn’t particularly like watir-webdriver, esp since I originally came from using the original Selenium RC Java bindings. Using the ruby bindings directly with CSS selectors seem more natural to me.

      As far as other custom functionality, Ruby is awesome in that its extremely easy to monkey-patch existing methods. So, you can open up the #click and #send_keys methods and patch them how you want and call the original method again. You’re not entirely tied to working with these AbstractEventListeners alone.

      You should come up with some examples and real life workings for your endless possibilities of ideas πŸ™‚ Do you have a blog that you post on?

      • I don’t – I have many times thought of starting a blog just like yours, but haven’t gotten around to it. However, there’s probably room for another, so I might spin it up some time soon… now that I know there’s someone out there that uses Webdriver on Ruby, without watir-webdriver. πŸ˜‰

  2. Pingback: A Smattering of Selenium #155 | Official Selenium Blog

    • Hi,

      I checked your article but it seems you are not using eventListener as mentioned in this post.. Correct me if i am wrong.

      • Well yes,

        My implementation is kind of raw, and I find it kind of easy to wrap it up into a core web element select implementation of my own.

        I just had a initial look at the AbstractListener and it has provisions only for the click operations and it lacks for things like WebElement.isDisplayed() where we can use to verify whether some particular text is present on a specific are on the web page.Click doesnt come here into the picture at all.

        I am not sure whether this is the same case for the Ruby bindings as well.

        Here is what I tried.I created a Listener that can be wrapped onto a EventFiringWebDriver as follows.

        —————————————————————————————–
        public class WebElementHighLighterAsEventListener extends AbstractWebDriverEventListener{

        public void beforeClickOn(WebElement element, WebDriver myTestDriver) {
        for (int i = 0; i < 1; i++) {
        JavascriptExecutor js = (JavascriptExecutor)myTestDriver;
        js.executeScript("arguments[0].setAttribute('style', arguments[1]);", element, "color: black; border: 3px solid black;");

        } }

        public void afterNavigateBack(WebDriver myTestDriver) {
        }

        public void beforeNavigateTo(java.lang.String url, WebDriver myTestDriver) {
        }

        public void onException(java.lang.Throwable throwable, WebDriver myTestDriver) {

        }

        }
        —————————————————————————————–
        I think it is up for you to decide,but I will not recommend AbstractWebDriverEventListener unless you are doing some heavy logging and you are using only the mentioned operations as in the source methods.Even in that case I would recommend custom logging statements to have a more exact stack trace.

        Please do let me know if I have mis-stated anything.I'll probably try a full implementation when I find time.

  3. Hi,

    I did came up with the AbstractWebDriverEventListener implementation of this,and I have updated my blog with the same
    Blog post link : http://technologyforthought.blogspot.in/2013/07/highlighting-web-element-in-selenium.html

    I found that the best way to do it is to wrap it inside the beforeFindBy,if there are better ways to do it,please do let me know,ill update the same in my blog.

    Sorry for the delayed reply,I was tied up with some other things.

  4. Pingback: A Smattering of Selenium #155 | Internet Deal

  5. Pingback: A Smattering of Selenium #155 | SQA Tutorial

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s