Monday, August 10, 2015

Selenium content supplier

Assuming that you've ever worked with Selenium, you know that to run tests in Chrome / Internet Explorer browser you should supply special so-called standalone server (chromedriver / IEDriverServer), which implements WebDriver wire protocol. When browser updates happen, most likely you'll need to update appropriate drivers as well. If you work with Grid, sometimes you may also need to update appropriate standalone server when new Selenium version appears.

Well, it could be quite bothering to keep external Selenium content always up-to-date. Would be nice to automate this process somehow, right? As you may guessed, this would be our primary goal for today.

Let's start with some introduction first. There're 2 public resources where you can find a list of available items for downloading Selenium content: selenium storage and chromedriver storage. Direct links will move you to the root XML view, which are useless from end-user perspective. But from developers point it's a mine of information. If you look at these XMLs in details, you'll notice that there's a list of Contents nodes, and each of them contains a special Key. You may wonder how could it help us with our main task? In fact, this key is the last part of an end-point for downloading particular content. So if we concatenate a root URL with one of the listed keys, we'll get a full download URL of any available resource. And it means that we can use a simple GET request for retrieving any content we want. Well, it's quite easy task if you know which particular version is newest. But in fact, we don't have such information. Or we do? Let's take a look at our XML again:
As you can see, all the keys are sorted, so if we could parse this XML somehow and retrieve last node's info, it'd resolve our issue with latest version recognition.

Fortunately, we use IntelliJ IDEA, which can generate XSD schema from XML:


And then we can generate source code from XSD schema:


So to get our XML model, we just need to save it somewhere in project, and everything else could be done in several clicks using our favourite IDE.


Well, now we have a model, and it means that we could send a simple GET request to the root URL to retrieve a list of available contents and put everything into newly generated entities. I prefer REST approach, so let's see how we could do that with Jersey client:
First method tries to get XML content and put it into ListBucketResultType.class. Second overloaded method loops through each content node, applies filtering by key and returns last matching value. You may wonder, what Content type is about? It's a custom interface, which is intended to provide access to common Selenium content wrappers: ChromeDriver, IEDriver and SeleniumServer. As we may want to download different resources of different OS types / bitness, it was necessary to make our code generic.
As you can see, there was created some preceded configuration for easier content parsing. Each wrapper contains its own set of characteristics. Let's see how we can use this code with content downloading API:
As you can see, we're passing exact content type we want to download. Next goal is to parse XML and find out latest key using getLatestPath API. Received key should be additionally splitted, as it contains the following format - version/resourceName. Now we're ready to prepare a new saving path according to known info about resource name and output folder. When it's done, we just need to send a GET request to remote end-point and read appropriate response into InputStream. To save received file data, we may want to use Apache IOUtils copy API. The last step is to return a saved file name for further processing.

You may wonder, what kind of processing do we else need? Well, some of available artifacts are zipped. So it'd be nice to perform automatic unzipping when downloading is completed, right? That's why we return a saved file name. To unzip items we may want to use some existing library, like zip4j:
There's nothing specific in this code that needs to be discussed, so I'll leave it here for your own investigation.

Cool, so now we can download and unzip any Selenium content we want. But what's about remote delivery? If we're working with Grid, we may want to update not only a hub VM, but all the nodes as well. For this particular case there was added a server platform, with a simple file upload service:
Note that Java 8 parallel streams allows us to increase performance while files processing. Besides that, there was added unzip functionality as well. Now let's take a look at client API for file uploading:
Here we prepare a MultiPart files content for further sending according to passed list of paths. By default, unzipping feature is enabled. But it's safe, as backend side uses zip extension filtering.

That's it. Let's see some test samples to understand how it's easy to download latest Selenium content using this library:
In a first example we try to download the entire Selenium content and unzip it in the same output folder. Second sample represents how to download particular resources and send them to remote VM.

You may wonder, where do we need to specify ip / port of a remote server. It could be done via the main client class parametrized constructor:
For local files processing you may want to use just a default constructor.

That's all about Selenium content supplier. Hope you'll find it useful for your projects. By the way, you can combine it with EnvironmentWatcher service to implement the following scenario on fly: stop all services -> update Selenium content -> raise up the entire automation staff with new jars / drivers.

You can download sources on GitHub. And related samples as well. Main project is not in official Maven repository yet, so you need to build it by your own. Just use mvn clean install command to generate appropriate artifacts, and then you can add the following dependency to your own projects:

Sunday, August 2, 2015

Java 8 impact on test automation framework design - Part 2

In the first part I've shown you how to prettify UI tests using Java 8 interfaces. This time, we'll take a look at a more complicated example.

Well, you may know that there're 2 common ways of accessing web controls via WebDriver:
  1. @FindBy + WebElement -> automatic lookup with PageFactory.initElements.
  2. By -> delayed lookup with driver.findElement or WebDriverWait + ExpectedConditions.
Personally I prefer second option, as it gives better flexibility, while working with complicated JS-based websites, when we always need to wait for something. But on the other hand, By class seems a bit non-obvious, plus there's no factory implemented for this case yet. Well, actually in the first part we did created a custom factory, so we could say that this problem has gone. But besides that, it'd be nice to see some similar elements' definition style, as it was implemented for pure WebElement.

In this article I'll show you how to create custom typified elements and a generic initializer, similar to mentioned above initElements.

We'll modify some part of code from one of my previous articles, as the idea remains the same: creating a custom HTML annotation and HTMLElement class. But this time we'll also implement some more specific elements, like Button, TextInput, Label, etc., mostly as it was done in Html Elements framework.

Let's see HTMLElement (aka base element) first. It won't be a full listing, only some key moments:
As we're going to create more specific elements, it's important to pass WebDriver instance to our base element's constructor for further usage in a combination with WebDriverWait. To be honest, there're lots of ExpectedConditions we may use for element's locating, but for educational purposes we'll look only at the most popular: visibilityOfElementLocated, presenceOfElementLocated and elementToBeClickable. All these conditions could be described via the following function:
This function was applied in a generic waitUntil method, so that we could pass any of listed above ExpectedConditions as a parameter. Now we're ready to create some more specific elements, e.g. TextInput:
As you can see, it extends HTMLElement. Now we can use waitUntil method to locate only those inputs which are clickable. Besides that, we've defined a custom logic: clear input and type some text. Note that element's locator reference is physically located in a super class.

Let's assume that we've already created a set of specific elements. So how could we initialize them? If you look at PageFactory sources, you'll notice some dark reflection magic. It'll take you some time to figure out how it's implemented. But I'll show you an alternative, even more generic and darker way of elements' initialization. It'll be still reflection-based, but with a help of Java 8 features we'll see how it could be implemented within single interface.

To make this experiment more realistic, we'll create some other type of element, so that we could see that our elements' supplier is not hardly dependent from a single type and is definitely generic. So what type of element would it be? Have you ever heard something about SikuliX? It's an OCR tool, which may help us to resolve some complicated automation tasks, which are impossible with Selenium. So before starting with elements' initializer, I'll create a model for SikuliDriver, ScreenElement and its ImageElement implementation. Well, hope some time in the future I'll have enough capacity to implement a full functional approach to make SikuliX closer to WebDriver interface. But for now it'll be just a mock.

Here's a draft implementation, which will be further mocked in test:
There won't be any real clicks or text typing, but we need to know that element has been successfully initialized and we could perform some basic actions.

Well, our alternative model is ready, so now let's add WebDriver and SikuliDriver(mock) into BaseTest class.
Note that well-known initialization / quiting staff was skipped, but you can find a full source later on GitHub. Mockito library was used for mocking, so don't forget to add appropriate dependency into root pom.xml:
And now it's time for something very special. Welcome, our magic interface - ElementsSupplier. I'll try to explain everything within the following listing, as there's a complicated combination of reflection, streams, lambdas and default methods.
Let's start with the end. As you may noticed from HTMLElement and ImageElement constructors' signature, both receive specific drivers as a fist argument, which are needed for further elements locating:
We also have 2 custom annotations - HTML and Image, which values need to parsed and supplied to appropriate elements' constructors side by side with mentioned above drivers. It's a bit tricky moment. In case of a single element's type, we know exactly which annotation to parse, which driver to use and which constructor to call. But our case is more generic. We don't know exactly how many elements' types, drivers and annotations are there. So we can't predict which constructor to call. Here's our first requirement: a class which implements ElementsSupplier interface must provide a list of supported drivers and annotations:
In case or drivers, we may want a Stream of their instances for further passing to matching constructors. In case of annotations, we just need their types to detect if particular instance variable contains one of supported items. Now let's take a look at our BasePage class, which implements ElementsSupplier:
As you can see, we've overridden both abstract methods to provide WebDriver and SikuliDriver instances, as well as HTML and Image annotation types. Now our interface knows a search direction (annotations) and first constructors' arguments (drivers' instances).

We can also see that BasePage default constructor explicitly calls default initElements method, passing this as a parameter. You may wonder, what does this mean in such context? It's a reference on a top-level PageObject, which we have triggered to be initialized. So we ask our interface to initialize all the custom fields within particular class and its super-classes.

Now let's take a look at common algorithm for elements' initialization:
  1. First we need to loop through each declared field of a current PageObject class and its super-classes, until we reach a base Object.class. We can do that with Stream.iterate API. But with one important note. Pure java implementation doesn't support any good exit criteria except setting limit operation. By default we don't know a number of super-classes we want iterate, so the only valid condition for us will be !currentClass.equals(Object.class). Fortunately, there's a great Streams extension library com.codepoetics.protonpack.StreamUtils, which allows us to set appropriate Predicate to break an infinite loop, when condition is met (takeWhile API).
  2. Next we need to loop through all declared class fields and find out, if any of supported annotation types is present.
  3. If anything found, we retrieve annotation by its type and call specialized initElement method for further field initialization.
  4. initElement itself could be splitted into several logical parts. First of all, we need to retrieve all annotation values. It's a bit tricky part, as getDeclaredMethods() API doesn't guarantee to return an ordered list of methods (how they were declared in a class). But order is very important while passing arguments to appropriate constructor. That's why we are using custom methods' comparator (by name), which meets our order requirements. But anyway you could always override default methodsComparator() with your own custom logic.
  5. These are annotations' arguments, but what's about drivers? Our constructors require particular driver instance as a first parameter. Here's the other tricky moment. Both drivers are of generic interface type. And there's no easy way to guess which exact type is assigned to particular object. That's why we have to loop through all supported drivers, insert one to the beginning of annotations' arguments list and pass it deeper to createInstance method.
  6. createInstance uses common java reflection API to intialize our custom elements with provided arguments list. As I've mentioned above, there's no easy way to detect assigned interface type, so we additionally try to check whether WebDriver or SiluliDriver types are assignable to provided arguments. If yes, we return more specific type to be able to find matching constructor. In case of any exception, we return empty Optional. It means that there's no matching constructor found for particular combination of a driver / annotation arguments, and we should try another driver as a first parameter.
  7. The final step is to check if any object instance was created. In a positive case we make field accessible and put a newly initialized reference inside.
That's it. Now we can make sure that all the fields are initialized. There's only 1 note left. As SikuliDriver is mocked, we should also mock ScreenElements to check if our approach works. It could be done somewhere in @BeforeMethod.
Let's declare the same items e.g. in HomePage:
And add appropriate call into test case:
Well, there's no any valid logic in uploadFile call. The only purpose of this is to see a working WebDriver test with appropriate SikuliX console log messages:


That's all. You can find sources as usual on GitHub.