Search

Sunday 17 March 2013

Sirius: adding Web client API

We've done some work for Web core functionality. To make the work complete the client API should be added in the same fashion as for Win32 library. At the end of current development there should be an example how to interact with web using Web client.

The following topics will be covered here:

  • General Web client architecture
  • Web client proxy updates
  • Web client class library implementation

General Web client architecture

As before we're keeping the same object model for client API. It means that all our pages and elements are wrapped with appropriate classes. Generally the class structure has the following view:

There are 2 major groups of classes:
  • Page classes - represent objects operating with element containers like pages, frames and forms
  • Controls - represent objects wrapping functionality interacting with specific controls
Each of them has it's own set of classes:
  • Page classes:
    • Frame - common class for any web window level object. It's not necessaryly frame but also any window. Actually, in web the main window is particular case of frame, so the frame is just some generalization of page-level objects
      • Page - represents class interacting with the entire page and in addition to frame functionality it has methods for mavigation
      • Form - it's just the wrapper on elements container. Actually it's the same control but as the frame it can be a container of controls.
  • Controls
    • WebControl - common class for all web controls. Contains very generic functionality which is the same for any control type
      • WebEdit - provides functionality for interaction with text fields
      • WebPopupList - provides functionality for list boxes
      • WebButton - provides common functionality for any buttons and all related control types
        • WebLink - additionally provides interface for attributes/methods specific to the links
        • WebImage - additionally provides interface for attributes/methods specific to the images
        • WebRadioButton - additionally provides functionality for getting/setting check marks
          • WebCheckBox - additionally unchecks the check mark. That's the biggest difference from radio button
These are the major classes to be added for client side

Web client proxy updates

Once we generated the client for web core functionality we should make some updates related to token processing. When we start new web driver instance on client side we can keep communication session with it only using token. Actually, the token is needed every time we call the server methods. From development point of view it's just an overhead as we explicitly have to store the token value after each driver start and them explicitly pass it as the parameter. However, it would be much more convenient if we wrap the token handling on the client class level as each client session corresponds to web driver session.

For this purpose we extend generated WebCoreProxy class and add the following wrappers:

public class WebClientCoreProxy extends WebCoreProxy {

 protected String token = "";
 protected WebSelectProxy select;
 
 public String token(){
  return token;
 }
 
 public String start(String browser) throws RemoteException {
  this.token = super.start(browser);
  return this.token;
 }

 ...
}
Note the highlighted part. When we start web driver we get it's token. After that we store token value in the local variable which is used in any further calls. With this improvement all inherited methods are redefined in the following form:
 public void sendKeys(String arg1, String arg2, String arg3)
   throws RemoteException {
  
  super.sendKeys(this.token, arg1, arg2, arg3);
 }
As we can see we reduced the number of arguments to pass and token is transfered implicitly. That's the first step.

Next step is to create main client class for Web interaction. It should contain:

  • Reference to client proxy class
  • Start and stop methods for web driver as we should do that explicitly to initialize client
So, we should add the class which looks like:
public class WebClient {
 
 private WebClientCoreProxy core = null;
 
 public WebClient() {
  core = new WebClientCoreProxy();
 }

 public WebClientCoreProxy core(){
  return core;
 }
 
 public void start(String browser) throws RemoteException{
  core.start(browser);
 }
 
 public void stop() throws RemoteException{
  core.stop();
 }
}
Complete source code can be found here.

Web client class library implementation

Now it's time for client API wrapping the classes. The main class here is the Frame. It has the following content:

public class Frame {

 protected WebClient client = null;
 protected String locator = "";
 
 public Frame(WebClient client,String locator) {
  this.client=client;
  this.locator=locator;
 }
 
 public String getURL() throws RemoteException{
  return this.client.core().getURL();
 }
 
 public String getLocator(){
  return this.locator;
 }
 
 public Point getLocation() throws RemoteException{
  return this.client.core().getLocation(null, locator);
 }
 
 public Dimension getSize() throws RemoteException {
  return this.client.core().getSize(null, locator);
 }
 
 public String innerHtml() throws RemoteException {
  return this.client.core().getAttribute(null, locator, "innerHTML");
 }
 
 public void switchTo(Frame frame) throws RemoteException{
  this.client.core().selectFrameByName(frame.getLocator());
 }
}
The key part is highlighted. The Frame class is the only place where we keep the reference to the client. All other classes either inherit that field or get it from the referenced classes.

The Page class inherits all Frame functionality + adds some navigation methods:

 public Page(WebClient client,String locator) {
  super(client,locator);
 }

 public Page(WebClient client) {
  super(client,null);
 }

 public String title() throws RemoteException{
  return client.core().getTitle();
 }
 
 public void back() throws RemoteException{
  client.core().back();
 }
 
 public void forward() throws RemoteException{
  client.core().forward();
 }
 
 public void refresh() throws RemoteException{
  client.core().refresh();
 }
 
 public void open(String URL) throws RemoteException{
  client.core().open(URL);
 }
 
 public void switchTo(Page page) throws RemoteException{
  client.core().selectWindow(page.getLocator());
 }
These are the top level window classes.

Control classes have only one major difference: they have references to parent elements and retrieve client from those parent elements. So, e.g. for WebControl class (the main class for all web controls) we use the following code:

public class WebControl {

 protected WebClient client = null;
 protected Frame parent = null;
 protected String parentElement = null;
 protected String locator = null;
 
 protected WebClient client(){
  if(client == null){
   client = parent.client;
  }
  return client;
 }
 
 public WebControl(Frame parent,String parentElement, String locator) {
  this.parent = parent;
  this.parentElement = parentElement;
  this.locator = locator;
 }

 public WebControl(Frame parent,String locator) {
  this(parent,null,locator);
 }
 .............
}
The highlighted part shows the major reference to the client. It's the wrapper method which retrieves the client reference from parent object unless it's already initialized. With this method we call for web client core functionality in the following form:
 public String innerText() throws RemoteException{
  return client().core().getAttribute(parentElement, locator, "innerText");
 }
All other classes are designed in the same fashion calling appropriate client methods. I won't describe them all in details. They all can be found here.

Sample

At the end once we've created base library we're ready for some small example. We'll open Google page, enter Hello World!!! text and click on Search button. We'll do it in several stages.

Declaring window objects

Firstly, we should define the main window containing controls for text field and button. It's done the following way:

    public class GoogleStart extends Page {
     
     public WebEdit edtQuery = null;
     public WebButton btnSearch = null;
     
     public GoogleStart(WebClient client){
      super(client);
      edtQuery = new WebEdit(this,"name=q");
      btnSearch = new WebButton(this,"name=btnG");
     }
    }
So, we have page class and 2 fields corresponding to our text field and button.

Initializing client

Once declaration is ready we can initialize our client and initialize the window object. It's done with the following code:

        WebClient client = new WebClient();
        client.start(WebClient.HTMLUNIT);
        GoogleStart main = new GoogleStart(client);
In this example we're using the headless browser however we can specify any other browser.

Writing instructions

Once we're done with initialization we can write our instructions:

  //Open the page
  main.open("http://google.com");

  // Enter text into text field
  main.edtQuery.type("Hello world!!!");
  
  // Verify the text content
  Assert.assertEquals(main.edtQuery.getValue(),"Hello world!!!");
  
  // Press the search button
  main.btnSearch.click();
After that we can run the test and see the results.

Conclusion

Thus we've covered web part. Of course, there're a lot of things left to do to make the framework more flexible and stable. E.g. we should wrap synchronization functionality as well as update some missing methods. But at the moment we have a good basis for web testing. And even more. Since we already have Win32 library we can combine web application interaction with some base GUI operations. And eerything is done under the same platform.

No comments:

Post a Comment