I Write Things

Selenium Click Capturing

Context

If you've worked with Selenium for any length of time you'll have encountered a test where Selenium occassionally errors out with:

Element is not clickable at point (x, x). Other element would receive the click  

In my experience with Selenium, I've found the click capturing to be one of the more finicky processes. Let's take a look at what's happening.

Why this happens

There are a couple of reasons that another element is recieving a mis-intentioned click. One is that the test is legitimately failing. That is, if a user were to try to click on the jQuery element, another element (like a menu) is over the intended target, obscuring it in some way.

With tests that are failing sporadically, especially if the click happens soon after page load, there may be another mechanic at work.

In our tests, when a page was first loading, Selenium would find a button to click and in the time between when the button coordinates were located and the mouse was clicked, CSS reflowing caused the position of the button to be relocated.

Selenium Click Capturing

Possible (python selenium) Solutions

Use jQuery to click

If you are using jQuery in your project you can leverage the jQuery click method to click on the button.

def jquery_click(driver, css_selector):  
    driver.execute_script("$(\"" + selector + "\").click();")

Wait until it's clickable

If you prefer to use the UI (which, after all, is how your end user will be accessing the element), you have to be a little more clever.

One option is that you can wait until the UI stops moving as demonstrated in code below:

import time  
from selenium.common import exceptions as s_exc  
import selenium.webdriver.support.ui as ui

def wait_until_clickable(driver, css_selector):  
  driver.wait = ui.WebDriverWait(driver, 30)

  def stopped_moving(driver):
    try:
      element = driver.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, css_selector)))
      initial = (element.location, element.size)
      time.sleep(0.05)
      element = driver.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, css_selector)))
      final = (element.location, element.size)
      return initial == final
    except s_exc.StaleElementReferenceException:
      return False

  driver.wait.until(stopped_moving)
  return driver.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, css_selector)))

elaborate click function

In our integration tests we use a combination of the two methodologies above creating an elaborate click function that we use instead of the simple click() provided by selenium. This has drastically cut down the flakeyness of our tests.

Other reasons click() may not work

Make sure the middle is clickable

In selenium, the click() function clicks on the middle of an element. If your element is unclickable at that point you're going to have a problem.

Make sure the window containing the clicked element is in front

Sometimes when you're not looking the window you are trying to interact with will be backgrounded. Check out this article on foregrounding the window- Working with Multiple Windows


Ignite San Francisco

I've been to Mission High school twice in the past couple of months for an event put on by Ignite, an organization started in Seattle to connect young women in middle and high school with professional women working in STEM fields.

These are low-key events where small groups of high school women gather in a classroom and spend 10-15 minutes chatting with each of speakers about their experiences leading up to their career in a STEM field. And these careers were varried! We had game designers, and structural engineers. Biomedical engineers and environmental analysts. Data analytics and software engineering.

Being able to chat with other women about the choices they'd made, struggles they'd faced, and successes they'd acheived was certainly great for these young counterparts:

"I really like learning what people do in life because I could grow up and take their footsteps."

"The most exciting thing to me today was learning about all the different types of jobs there are and even though women aren't in that many technical jobs, more and more are going in every day."

"Not all of them were interested in what they are doing now when they were my age"

"I saw the inside of a Mac."

Ignite San Francisco Mission High School

Ellen Norton talks about her experiences in STEM with young women from Mission High School

However, for me I got even more out of the experience than these young women did. I realize that it's rare for me to interact with so many interesting, qualified women. I am the only woman in my development team and have been my whole career starting with my first mechanical engineering internship at OSRAM Sylvania.

Sure, I go to women who code events and the like but there's something unique about hearing a bunch of other women talk passionately about what they do and encourage others to follow their footsteps. It reminds me how lucky I am.


Proper and Improper Uses of onShow() and onRender() in Marionette

Context

For the past few months, I have been consistently stymied by Marionette's built-in onShow and onRender methods.

In a single-page app, you often manipulate the DOM Tree instead of doing full page reloads. Marionette.js is a handy backbone library that provides a clean way to execute this manipulation in the form of its Layouts and Views. It is often convenient to know when a View's DOM subtree has successfully been inserted into the DOM and is jQuery accessible.

This is where the onShow() and onRender() methods can come in handy.

What the docs say

onRender

"render" / onRender event

Triggered after the view has been rendered. You can implement this in your view to provide custom code for dealing with the view's el after it has been rendered.

from Marionette ItemView Docs

onShow

A region will raise a few events when showing and closing views:

"show" / onShow - Called on the view instance when the view has been rendered and displayed.

"show" / onShow - Called on the region instance when the view has been rendered and displayed.

from Marionette Region Docs

This does not present us a full picture of why, when, and how we should use these methods.

What the docs should say

onRender

onRender() was a misleading term for me. "onRender", I said to myself, "The sounds like it would be invoked when DOM elements have been rendered by the browser."

False

onRender() gets triggered when the View's DOM subtree is prepared for insertion into the DOM but before those elements are viewable. Putting code in the onRender function of your view will NOT guarantee that you will have jQuery access to these newly created elements.

onShow

The onShow() method is used in the context of Layouts and Regions. A Marionette Layout may have many "Regions" that will later be populated with sub-Views. These sub-Views are instantiated when a Layout invokes its region's .show() method with the View to be rendered.

When .show() is invoked, the DOM subtree for that sub-View is constructed and inserted into the DOM. A "show" event then triggers any code in the onShow() methods in both the Layout and the sub-View.

Proper Use

Code in onRender() will be executed reliably every time this.render() is called by the view. You should include code that needs to be called every time you would update that View. onRender() does not assure you jQuery access to the View's DOM elements. Code that relies on DOM elements should be in the onShow() method.

Code in in both the Layout and the sub-View's onShow() methods will be executed every time someRegion.show(subView) is called by the Layout. The Layout may not call .show() every time you update that sub-View. Code that needs to be run on every rendering should be in the sub-View's onRender().

Improper Use

The triggering of onShow() methods will behave unexpectedly if you do not handle handle nested .show() method calls carefully. Below is a diagram of a concrete example of deeply nested layouts and views that illustrate this:
Deeply nested views

Layout A Snippet

regions: {  
  regionAOne: ".region-a-one",
  regionATwo: ".region-a-two"
},

onRender: function() {  
  this.regionAOne.show(LayoutB);  // wrong
  this.regionATwo.show(ViewA);
}

Layout B Snippet

regions: {  
  regionBOne: ".region-b-one",
  regionBTwo: ".region-b-two"
},

onRender: function() {  
  this.regionBTwo.show(ViewC);  // wrong
  this.regionBOne.show(ViewB);
}

View C Snippet

onShow: function() {  
  this.$el.addClass("class-name");
}

In this example, in View C we will run into a javascript error: cannot call addClass of undefined. But wait! Shouldn't View C's onShow() method only be invoked if View C's DOM Tree elements are properly in the DOM?

We see this unexpected behavior because in the deeply nested layouts, rather than waiting for Layout B's DOM subtree to be viewable, View C is instantiated in the onRender() method. We see the same in Layout A in regards to Layout B.

In this case the order of events is as follows:

  1. Layout A comes into being (probably from a .show() call by its parent.

  2. When Layout A's DOM subtree has been constructed Layout B and View A are instantiated using regionAOne and regionATwo's .show() methods.

  3. When Layout B's DOM subtree has been constructed View B and View C are instantiated using regionBone and regionBTwo's .show() methods.

  4. Layout A's DOM subtree is visible in the DOM.

  5. onShow() in Layout A, Layout B, View C, etc. are all triggered at the same time.

  6. Layout B's DOM subtree is visible in the DOM.

  7. View C's DOM subtree is visible in the DOM.

Corrected Layout A Snippet

To ensure that each DOM subtree is inserted into the DOM before the onShow() method is called, move the region .show() code to the onShow() method of the Layout:

regions: {  
  regionAOne: ".region-a-one",
  regionATwo: ".region-a-two"
},

onShow: function() {  
  this.regionAOne.show(LayoutB);  // correct
  this.regionATwo.show(ViewA);
}

If you are unable to access jQuery DOM elements in an onShow() method, trace your way through your Layout/View hierarchy and find the culprit whose region calls .show() in a non-onShow() method.