//
you're reading...
Grid2

Working with a custom capability matcher in the Grid

I have been wanting to share my experiences on how to go about building a custom capability matcher to the Grid2 for quite sometime but haven’t been able to find time until now. So without further adieu here’s how you go about doing it :

What is a capability matcher in the Grid2 world ?
The capability matcher is what decides on which test should be routed to which node in the grid. If there aren’t any nodes that match what the user is asking for via their desired capabilities it’s upto the capability matcher to say so.

What are all the default parameters that the Grid2 considers when doing capability matches ?

The Grid2 considers the following 4 parameters when matching tests to a specific node on which it can be run.

  1. Platform
  2. Browser name
  3. version (Browser version)
  4. “applicationName” [ This is not documented much anywhere, but trust me this capability does exist ]

So lets begin by building our own capability matcher. We would first begin by either :

  1. Extending DefaultCapabilityMatcher (or)
  2. Implementing the interface CapabilityMatcher

In our example we are going to be taking approach (1) since we want all the good stuff that DefaultCapabilityMatcher gives us out of the box and just want to tweak it a bit more.

So we will define a custom CapabilityMatcher which could look like below :

package organized.chaos;

import java.util.Map;

import org.openqa.grid.internal.utils.DefaultCapabilityMatcher;

public class CrazyCapabilityMatcher extends DefaultCapabilityMatcher {
    private final String crazyNodeName = "crazyNodeName";
    @Override
    public boolean matches(Map<String, Object> nodeCapability, Map<String, Object> requestedCapability) {
        boolean basicChecks = super.matches(nodeCapability, requestedCapability);
        if (! requestedCapability.containsKey(crazyNodeName)){
            //If the user didnt set the custom capability lets just return what the DefaultCapabilityMatcher
            //would return. That way we are backward compatibility and arent breaking the default behavior of the
            //grid
            return basicChecks;
        }
        return (basicChecks && nodeCapability.get(crazyNodeName).equals(requestedCapability.get(crazyNodeName)));
    }

}

As you can see, we are now defining a custom capability named “crazyNodeName”. If a test is requesting for a capability which has this set, then the Grid will attempt to match all nodes which has the same value as the test requested for and attempt to route the tests to it. Did that sound Greek and Latin ?? Don’t worry. I will explain.

So now that we have the custom capabilities matcher built, our next step would be to inject this into the Grid when spawning it up.

This is how you do it.

First we would need to create a Hub Configuration file. It can look like below :

{
  "capabilityMatcher": "organized.chaos.CrazyCapabilityMatcher",
  "throwOnCapabilityNotPresent": true,
}

In case you are wondering what are all the contents of a hub configuration file, please take a look here.

Now lets spawn our Grid with this custom capability matcher. For that make sure you have built a jar out of your project which contains your custom capability matcher [ CrazyCapabilityMatcher in this case ] and then copy it to the same directory as where you standalone jar resides.

Now spin off a Hub using the command (OSX/Linux):

java -cp *:. org.openqa.grid.selenium.GridLauncher -role hub -hubConfig hubconfig.json

Note: Incase you are doing it on Windows, your command would look like below :

java -cp *;. org.openqa.grid.selenium.GridLauncher -role hub -hubConfig hubconfig.json

That should have a Grid spawned for you and you can check if the capability matcher that you injected was honored or not by the Grid by viewing its configuration at Grid Config

Now we would need to spawn a Node and hook it on to the grid. Before we do that we would need to define a custom configuration for the node that uses this new capability that we defined. It can look like below:

{
  "capabilities":
      [
        {
          "browserName": "firefox",
          "maxInstances": 5,
          "crazyNodeName": "Rambo"
        }
      ],
  "configuration":
  {
    "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    "maxSession": 5,
    "port": 5555,
    "host": ip,
    "register": true,
    "registerCycle": 5000,
    "hubPort": 4444,
    "hubHost": ip
  }
}

If you are interested in looking at what are all the other values that can dwell within a node configuration take a look here

And here’s how the node gets spawned

java -jar selenium-server-standalone-2.39.0.jar -role node -hub http://localhost:4444/grid/register -nodeConfig nodeconfig.json

As you can see,we have now added a custom attribute to this node and called it “Rambo”. So any test that either requests for firefox alone or requests for firefox and “Rambo” alone would be serviced by this node. For all other requests, the Grid shall politely refuse to route the test to this node.

Still confused ?

Here’s a test that attempts to use this setup:

package organized.chaos;

import java.net.MalformedURLException;
import java.net.URL;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class CrazyNodeTest {
    WebDriver wd = null;

    @Test
    public void f() {
        wd.get("http://www.facebook.com");
    }

    @BeforeClass
    public void beforeClass() throws MalformedURLException {
        DesiredCapabilities dc = new DesiredCapabilities();
        dc.setBrowserName(DesiredCapabilities.firefox().getBrowserName());
        dc.setCapability("crazyNodeName", "Rambo");
        wd = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"),dc);
    }

    @AfterClass
    public void afterClass() {
        wd.quit();
    }

}

Run the above test and see what happens. You would notice that your test got executed. [ This proves that our custom capability matcher worked ]

Now lets try and see what happens, if I dont set this capability at all. Here’s a test that explains what I am talking about.

package organized.chaos;

import java.net.MalformedURLException;
import java.net.URL;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class CrazyNodeTest {
    WebDriver wd = null;

    @Test
    public void f() {
        wd.get("http://www.facebook.com");
    }

    @BeforeClass
    public void beforeClass() throws MalformedURLException {
        DesiredCapabilities dc = new DesiredCapabilities();
        dc.setBrowserName(DesiredCapabilities.firefox().getBrowserName());
        wd = new RemoteWebDriver(new URL"http://localhost:4444/wd/hub"),dc);
    }

    @AfterClass
    public void afterClass() {
        wd.quit();
    }

}

As you can see, the test runs because we extended the DefaultCapabilityMatcher and then chose to honour its behavior if our custom attribute in question wasn’t passed on via the DesiredCapabilities.

Now lets see what happens if I set a different value to the custom attribute we defined. Here’s how the test looks like :

package organized.chaos;

import java.net.MalformedURLException;
import java.net.URL;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class CrazyNodeTest {
    WebDriver wd = null;

    @Test
    public void f() {
        wd.get("http://www.facebook.com");
    }

    @BeforeClass
    public void beforeClass() throws MalformedURLException {
        DesiredCapabilities dc = new DesiredCapabilities();
        dc.setBrowserName(DesiredCapabilities.firefox().getBrowserName());
        dc.setCapability("crazyNodeName", "Jim Carry");
        wd = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"),dc);
    }

    @AfterClass
    public void afterClass() {
        wd.quit();
    }

}

When you try running this you should see an exception as below :

[TestRunner] Starting executor for test Default test with time out:2147483647 milliseconds.
FAILED CONFIGURATION: @BeforeClass beforeClass
org.openqa.selenium.WebDriverException: Error forwarding the new session cannot find : {browserName=firefox, crazyNodeName=Jim Carry}
Command duration or timeout: 152 milliseconds
Build info: version: '2.39.0', revision: '14fa800511cc5d66d426e08b0b2ab926c7ed7398', time: '2013-12-16 13:18:38'
System info: host: 'localhost', ip: '127.0.0.1', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.8.5', java.version: '1.7.0_25'
Driver info: org.openqa.selenium.remote.RemoteWebDriver

Now I hope you understood how to leverage Custom Capabilities Matcher and then use it to your advantage.

Advertisements

Discussion

2 thoughts on “Working with a custom capability matcher in the Grid

  1. I found your post very useful for changing the capability matcher. After trying to use a custom capability with the steps you are referring i get exception: org.openqa.selenium.WebDriverException: Error forwarding the new session null (The sessionId is null). If i do not set the custom capability the session is forwarded to first available node. Are you aware of the issue?

    Regards,
    Giannis

    Posted by Giannis | January 30, 2014, 1:43 pm
  2. This was a great guide, thank you for posting it! I found this to be very useful when implementing my own capability matcher. This is the primary example I’ve found for how to do this and the first result on google when you search for custom capability matcher, so I wanted to point out a potential bug in this code with the hope of saving someone else the headache of debugging it. If you have a mix of nodes on the grid where some nodes do not have the custom capability in the node configuration and some do, then this capability matcher will throw a null pointer exception when it attempts to match a request with a node that doesn’t contain the key for the capability you’ve added (“crazyNodeName” in this example). The fix for this would be (using your example) to check that the nodeCapability map contains the key for crazyNodeName before trying to get it’s value, so then you’d have:

    return (basicChecks && && nodeCapability.containsKey(crazyNodeName) && nodeCapability.get(crazyNodeName).equals(requestedCapability.get(crazyNodeName)));

    Otherwise you will see the error message that Giannis mentioned, “org.openqa.selenium.WebDriverException: Error forwarding the new session null”. This can end up being a little bit of a subtle bug because you may or may not hit the exception case depending on the order of the nodes in your grid.

    Posted by MItchell | November 9, 2015, 9:49 pm

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

%d bloggers like this: