Skip to content
May 28, 2013 / selenium34

Long Break…

It’s been quite some time since this space has been updated. I wanted to give an update on our latest work.

We have begun switching to Geb. We are currently working on getting tests to run in parallel. Unfortunately as we are using Spock alongside of Geb, this is likely going to be a chore… 

If anyone has done any work on getting these tools to work in parallel, we’d love to hear from you.

Advertisements
February 22, 2013 / selenium34

Which bindings do you use? Which is the most interesting?

It’s been far too long since there has been any new content on this space. This actually wasn’t even the article I intended on being published next, but so it goes.

Obviously all of the examples on this site thus far have been written in Java, mainly due to experience with it on our team. However I have seen so many different bindings/DSLs for Selenium, that it has gotten me curious. What tools are you using? Is it the language you use in your day to day work? Is it a scripting language? Is it something else entirely?

We have looked at geb + spock, phantomjs + ghostdriver, watir + watirwebdriver, and at the native implementations (Java, C#, Ruby, & Python). What other tools are you looking at for your testing purposes?  Have you worked with any of these? What do you like/dislike about them? What do you feel is lacking in this space?

November 21, 2012 / selenium34

Blocking Third Party Hosts

Often times while testing you’ll want to ensure that you are testing your system in isolation. We find that our tests are often much slower due to third party integrations, it might be a social media provider or it could be a third party analytics provider. These items are outside of your control, and you may wish to avoid their effects while testing.

How can we do this? Pull in the browsermob-proxy project into your tests (we’d love to hear your comments on how this can be achieved with other language bindings). With this addition you now have easy ways to ensure your third party providers will not interfere with your testing. You might wish to run these tests with and without the proxy enabled to get an idea of how your end-users will perceive the performance of your site. With all of this being said, it is worth keeping these artifacts somewhere, and running eventually deriving some statistics from your data sets.

So how do we do this? First we need to setup our proxy server, this is as simple as adding the proxy to your class path, and some setup code. Remember we are using TestNG in this case below.

public class ExampleProxyTest {
  private ProxyServer server;
  private WebDriver driver;

  @BeforeClass
  public void setup() throws Exception {
    server = new ProxyServer(9000); //Any high numbered port should work fine
    server.start();
    for (final String s : getHostsToBlackList()) { 
      server.blacklistRequests(s, 200); //Send a 200 when the host matches
    }
    final DesiredCapabilities dc = new DesiredCapabilities();
    dc.setCapability(CapabilityType.PROXY, server.seleniumProxy());
    driver = new FirefoxDriver(dc);
  }

  private List<String> hostsToBlackList() {
    final List<String> hosts = new ArrayList<String>();
    hosts.add("http://.*\\.fbcdn.net/.*");
    hosts.add("http://.*\\.facebook.com/.*");
    hosts.add("http://.*\\.plusone.google.com/.*");
    hosts.add("http://.*\\.twitter.com/.*");
    hosts.add("http://.*\\.twimg.com/.*");
    hosts.add("http://.*\\.pinterest.com/.*");
    return hosts;
  }
}

As you can see we have blocked out a number of Social Media sites. This list should be modified for your needs obviously. However, now it’s as simple as opening your site, and noticing the speed increases.

@Test
public void testThirdPartyBlocks() {
  server.newHar("example.har");
  driver.get("http://example.com");
  try { 
    server.getHar().writeTo(new File("example.har"));
  } catch (IOException ioe) { }
}

That’s it. You should see a nice increase in testing speed now, you could also test to see how your application works / looks when your JavaScript fails to load, or a myriad of other things. Don’t forget to close the driver, and shut down the proxy:

@AfterClass
public void shutdown() {
  driver.quit();
  server.stop();
}

If you hadn’t seen our post on changing browsers without changing code, it’s worth noting that we did not show you that we also have the proxy in a profile. If that profile is turned on than the driver we return has the proxy enabled. It will be nice once the cloud providers can allow us to use this functionality on their sites.

Until next time, enjoy the Thanksgiving holiday if you’re celebrating it.

October 26, 2012 / selenium34

Code finally on github

We’ve finally added the code to github. It’s pretty raw (not commented), pull requests will certainly be considered.

October 18, 2012 / selenium34

Changing Browsers without changing Code

I stated in our last post that we would be putting our code up on GitHub soon… I apologize for that not happening, but I really wanted to get this out in the open before we added our bits to GitHub. As usual that did not happen in a timely manner. For this I apologize.

We are introducing two concepts from the Spring Framework in this post. Spring is a bolt on product for Java development, which is typically used in enterprise applications. We will be using two new features of the framework. Namely, Java configuration and profiles. To ease this transition, I am switching from JUnit tests to TestNG. This is due to JUnit @Before & @After methods needing to be static. Spring cannot inject static values.

Below is our updated class (I’ve removed all but one of the tests to shorten the post):

@ContextConfiguration(classes = { Firefox.class, InternetExplorer.class, Chrome.class })
public class JQueryHomePageTest extends AbstractTestNGSpringContextTests {

  @Resource
  private WebDriver driver;
  private JQueryHomePage homePage;

  @BeforeClass
  public void setup() {
    homePage = new JQueryHomePage(driver);
  }

  @Test
  public void getText() {
    //Chrome and Firefox will include a \n in the text representation, whereas IE will not
    final String text = StringUtils.replaceEach(homePage.getTagline().getText(), new String[] { "\r", "\n" }, new String[] { " ", " " });
    Assert.assertTrue("jQuery is a new kind of JavaScript Library.".equalsIgnoreCase(text));
  }

  @AfterClass
  public void shutdown() {
    driver.quit();
  }
}

Now we need to setup our configuration files. Here is what our Firefox class looks like:

@Configuration
@Profile("firefox")
public class Firefox {
  @Bean
  public WebDriver driver() {
    return new FirefoxDriver();
  }
}

And the Chrome class:

@Configuration
@Profile({ "chrome", "default" })
public class Chrome { 
  @Bean
  public WebDriver driver() {
    System.setProperty("webdriver.chrome.driver", "chromedriver.exe");
    return new ChromeDriver();
  }
}

And the InternetExplorer class:

@Configuration
@Profile("ie")
public class InternetExplorer {
  @Bean
  public WebDriver driver() {
    System.setProperty("webdriver.ie.driver", "IEDriverServer.exe");
    return new InternetExplorerDriver();
  }
}

If we run the test with no system parameters, we will utilize Chrome as our browser. If we add the following parameter to our run configuration “-Dspring.profiles.active=ie” we will run with Internet Explorer. If we add “-Dspring.profiles.active=firefox”, we will run with Firefox. This is a nice and easy way to modify our tests to run in a different browser, without having to modify code. Hopefully this is easily extensible for your environment. Need to run against Sauce Labs or Testing Bot? Create a profile(s) for each browser. Need to run against an internal grid? Create a profile(s) for each browser. If you can get this baked into your continuous integration server, you should feel pretty confident that your tests are behaving as expected in all browsers.

September 21, 2012 / selenium34

Child Elements

I’m sorry for the delay between these posts, but sometimes time gets away from us. When we look at our sites, we realized that they are very modular — look even at this site, we have a center column and a sidebar. We needed a way to verify that our modules were showing up in these correct areas, which led us to some initial frustration. Without storing your locator as static items you couldn’t do the following:

@FindBy(id = "jq-intro")
private WebElement introDiv;
@FindBy(css = "#jq-intro h2")
private WebElement introText; //Ensure that this is located within the introDiv

That wasn’t good enough; we wanted to enforce these types of rules, and yell out if items were located where they were unexpected (without having to use our jQuery locator or create a css locator based off the original). So today we are going to show you a slight spin-off of what we created to get around this limitation. This will again be a code heavy post, to keep it “short” there are no comments as in the past. This should be posted to github before long with some documentation / comments. I’ll update the blog once that has happened.

So let’s start (as usual) with a new annotation. We don’t allow for the $ here, as it seems unneeded — if you’re reverting to jQuery to find the item, it seems like that would be the proper locator to use. This will be the annotation that let’s us know that we should look inside of another WebElement:

public @interface ChildOf {
  String fieldName();
  How how();
  String using() default = "";
}

So let’s take the above example, and use our new annotation:

@FindBy(id = "jq-intro")
private WebElement introDiv;
@ChildOf(fieldName = "introDivWithDefault", how = How.TAG_NAME, using = "h2")
private WebElement introText;

Now we have to setup the plumbing for our PageFactory to find these items. To do this, we first need a ChildElementLocatorFactory:

public class ChildElementLocatorFactory implements ElementLocatorFactory {
  final Map<String, List<WebElement>> parentCandidates;
  public ChildElementLocatorFactory(final Map<String, List<WebElement>> parentCandidates) {
    this.parentCandidates = parentCandidates;
  }
  @Override
  public ElementLocator createLocator(final Field field) {
    if (field.isAnnotationPresent(ChildOf.class)) {
      return new ChildElementLocator(parentCandidates, field);
    }
    return null; //When the factory returns null, the PageFactory doesn't look for the item
  }
}

We also need to create our ElementLocator.

  public class ChildElementLocator implements ElementLocator {
    private final List<WebElement> parentCandidates;
    private final How how;
    private final String using;
    public ChildElementLocator(final Map<String, List<WebElement>> parentCandidates, final Field field) {
      final ChildOf childOf = field.getAnnotation(ChildOf.class);
      this.how = childOf.how();
      if (How.ID_OR_NAME.equals(how)) {
        this.using = field.getName();
      } else {
        this.using = childOf.using();
    }
    if (!parentCandidates.containsKey(childOf.fieldName())) {
      throw new NoSuchElementException("No parents with the field name '" + childOf.fieldName()
        + "' were located. Unable to locate element with: " + how + " using: " + using);
    }
    this.parentCandidates = parentCandidates.get(childOf.fieldName());
  }
  @Override
  public WebElement findElement() {
    NoSuchElementException finalException = null;
    for (final WebElement candidate : parentCandidates) {
      try {
        final WebElement e = candidate.findElement(buildBy());
        e.isDisplayed();
        return e;
      } catch (final NoSuchElementException nsee) {
        finalException = nsee;
      }
    }
    throw finalException;
  }
  @Override
  public List<WebElement> findElements() {
    for (final WebElement candidate : parentCandidates) {
      final List<WebElement> elements = candidate.findElements(buildBy());
      if (elements.size() > 0) {
        return elements;
      }
    }
    return Collections.emptyList();
  }

  private By buildBy() {
    //Implementation basically same as Annotations#buildByFromLongFindBy from Selenium code
    //Not showing here to shorten post
  }
}

And with that we need to update our PageFactory. This is long and uncommented, but again should be up on github soon.

public class CustomPageFactory {
  public static void initElements(final WebDriver driver, final Object page) {
    final Map<String, List<WebElement>> locatedFields = initElements(new CustomElementLocatorFactory(driver), page);
    initElements(new ChildElementLocatorFactory(locatedFields), page);
  }
  public static void initElements(final ChildElementLocatorFactory factory, final Object page) {
    PageFactory.initElements(new DefaultFieldDecorator(factory), page);
  }
  public static Map<String, List<WebElement>> initElements(final CustomElementLocatorFactory factory, final Object page) {
    return initElements(new CustomFieldDecorator(factory), page);
  }
  public static Map<String, List<WebElement>> initElements(final CustomFieldDecorator decorator, final Object page) {
    Map<String, List<WebElement>> locatedFields = new HashMap<>();
    Class<?> proxyIn = page.getClass();
    while (proxyIn != Object.class) {
      locatedFields = mergeValues(locatedFields, proxyFields(decorator, page, proxyIn));
      proxyIn = proxyIn.getSuperclass();
    }
    return locatedFields;
  }
  private static Map<String, List<WebElement>> mergeValues(final Map<String, List<WebElement>> foundInSubclasses,
    final Map<String, List<WebElement>> currentlyLocated) {
    if (foundInSubclasses.entrySet().size() == 0) {
      return currentlyLocated;
    }
    for (final Map.Entry<String, List<WebElement>> entry : foundInSubclasses.entrySet()) {
      if (currentlyLocated.containsKey(entry.getKey())) {
        final List<WebElement> locatedInSubclasses = entry.getValue();
        final List<WebElement> justFound = currentlyLocated.get(entry.getKey());
        locatedInSubclasses.addAll(justFound);
      } else {
        entry.setValue(currentlyLocated.get(entry.getKey()));
      }
    }
    return foundInSubclasses;
  }

  private static Map<String, List<WebElement>> proxyFields(final FieldDecorator decorator, final Object page, final Class<?> proxyIn) {
    final Map<String, List<WebElement>> locatedFields = new HashMap<>();
    final Field[] fields = proxyIn.getDeclaredFields();
    for (final Field field : fields) {
      final Object value = decorator.decorate(page.getClass().getClassLoader(), field);
      if (value != null) {
        try {
          field.setAccessible(true);
          field.set(page, value);
          if (locatedFields.containsKey(field.getName())) {
            if (!(value instanceof List)) {
              locatedFields.get(field.getName()).add((WebElement) value);
            }
          } else {
            if (!(value instanceof List)) {
              locatedFields.put(field.getName(), new ArrayList<WebElement>(Arrays.asList((WebElement) value)));
            }
          }
        } catch (final IllegalAccessException e) {
          throw new RuntimeException(e);
        }
      }
    }
    return locatedFields;
  }
}

Now we can add the following to our JQueryHomePageTest:

@Test
public void getText() {
  Assert.assertThat("jQuery\nis a new kind of JavaScript Library.",
  Matchers.equalToIgnoringCase(homePage.getTagline().getText()));
}

One thing to note: this will work with inheritance (on fields with the same name) as long as you annotate child elements with @OptionalElement.

September 6, 2012 / selenium34

Better Optional Elements

In yesterday’s post we discussed ways to allow Selenium to better handle items which may or may not have been on the page. We ended the post with something that might be considered a bit of a nuisance when attempting to utilize one of these non-located elements. Let’s take a look at the two tests we ended the post with:

@Test
public void ensureNullValue() {
  Assert.assertNull("A null value is expected for optional elements which do not exist on the page.", homePage.getOptionalIntroDivWithDefault());
}
@Test(expected = NoSuchElementException.class)
public void ensureNoSuchElementExceptionThrown() {
  homePage.getOptionalIntroDivWithDefault().getText();
}

Unfortunately the way proxies work in Java our proposed solution yesterday will not allow both of these two tests to pass. So, how can we ensure that the item is not located and retain the information from the NoSuchElementException?

Well, the easiest way is to create our own implementation of a WebElement. Here’s a portion of the class below:

public class MissingWebElement implements WebElement {
  private final NoSuchElementException nsee;
  public MissingWebElement(final NoSuchElementException nsee) {
    this.nsee = nsee;
  }
  @Override
  public void click() {
    throw nsee;
  }
  //Throw the NoSuchElementException for all methods the interface requires us to implement
}

It’s okay, you can admit it; you thought we were going to name that class CustomWebElement didn’t you?

Now we need to modify the CustomFieldDecorator to return a MissingWebElement when we have an optional item that was not located. We need to simply replace our decorate method from yesterday’s post, to what is shown below:

@Override
public Object decorate(final ClassLoader loader, final Field field) {
  if (!(WebElement.class.isAssignableFrom(field.getType()) || isDecoratableList(field))) {
    return null;
  }
  final CustomElementLocator locator = (CustomElementLocator) factory.createLocator(field);
  if (locator == null) {
    return null;
  }
  if (WebElement.class.isAssignableFrom(field.getType())) {
    final WebElement proxy = proxyForLocator(loader, locator);
    try {
      proxy.isDisplayed();
      return proxy;
    } catch (final NoSuchElementException nsee) {
      if (!locator.isOptionalElement()) {
        throw nsee;
      }
      return new MissingWebElement(nsee);
    }
  } else if (List.class.isAssignableFrom(field.getType())) {
    return proxyForListLocator(loader, locator);
  } else {
   return null;
  }
}

We now instruct our test to expect to receive a MissingWebElement:

@Test
public void ensureMissingElement() {
  Assert.assertTrue("A MissingWebElement is expected for optional elements which do not exist on the page.", homePage.getOptionalIntroDivWithDefault() instanceof MissingWebElement);
}