jueves, 8 de mayo de 2014

JBehave and Selenium best practices

Recently, I have been using JBehave with Selenium Web Driver in JAVA as a framework for Behavior Driven Development. By means, the JBehave framework aims to write stories and selenium web driver models and automates the web sites. So, I want to sum up some best practices and hits that I would suggest in order to face out some pitfalls that you may suffer when you play with these techs.


Some of these pitfalls:
  • The structure of the web site that we are automating changed in the latest deployment. (See 1,2,3)
  • Some of my utility class contains more than 500 lines of code. (See 4)
  • Internet Explorer is doing something different than Firefox (Any browser may behave in a different way, but IE is usually the winner). (See 4)
  • WebDriver is so slow. (See 5)
  • WTF after seeing some steps. (See 6,7,8)
  • No scenario test covering (See 9, 10)
If you have any of these problems, read on. 

1.- Don't try to model the web site per se. You should model a page by components and actions. 

For example:
  Home page 
            COMPONENTS:
                  a table
                       COMPONENTS:
                           a header, a body, a footer
                       ACTIONS:
                           select a row
                 a save button
                       COMPONENTS:
                           a label, a tooltip
                       ACTIONS:
                           redirect, submit
            ACTIONS:
                 select row and save

Therefore, there should exist a set of steps only suitable for only a page or component that check all actions involved on it.
For example:
 HomePageSteps.java
  givenAHomePage();
  thenISeeTheTable();
  thenISeeTheSaveButton();
  whenISelectTheRow(int rowNumber);
  whenIClickOnSaveButton();
  thenTableIsSubmitted();
 TableSteps.java
  givenATable();
  thenISeeAHeader();
  thenISeeABody();
  thenISeeAFooter();
  whenISelectTheRow(int rowNumber);
  thenTheRowIsHovered(int rowNumber);
 ButtonSteps.java
  givenAButton();
  thenISeeTheLabel();
  thenISeeTheTooltip();
  whenIClickOnIt();

Advantages:
- Decoupling code in Steps.
- Reusing steps and functionality when a page also contains table and buttons.

2.- Use Composite pattern to model your components and the Fluent interface design pattern (returning the own instance in any action).

For example, above Home page should look like:
public class HomePage extends Component implements HasTable, HasSaveButton {
 
 @Override
 public Table getTable() {
  return table;
 }
 
 @Override
 public Button getSaveButton() {
  return this.saveButton;
 }
 
 @Override
 public void initialize() {
  ...
 }
 
 public MyPage selectRow(int rowId) {
  this.getTable().selectRow(rowId);
  return this; 
 }
 
 public MyPage save() {
  this.getSaveButton().click();
  return this; 
 }
}

public interface Component {
 public void initialize();
}
Be aware, you may have many buttons in a page. 

Advantages: 
- Minimize changes made in development. If a page replace a table by a list, just remove the HasTable interface and create the HasList interface that must implement selectRow as well. 
- The usage of the page is more readable using fluent interfaces. For example, selecting a row and saving it: this.selectRow(1).save();

3.- Don't use FindBy tags on abstract classes or using carefully.

If you have a field with a FindBy and the by selector may change in the implementation of this class, you would have to duplicate the field and other messy things. So, let's an opened door for doing this. 
public abstract Page extends Component {
 @FindBy(id = "toast")
 private WebElement label;
 
 public WebElement getLabel() {
  return this.label;
 }
 
 public Page doingSomethingWithLabel() {
  this.getLabel().click();
 }
}
Therefore, overriding the getLabel method will have the proper WebElement. Anyway, I would avoid using FindBy tags in abstract class rather than overriding methods.

4.- Use Proxy pattern instead of Utility classes and common steps.

Most of times you need to have common functionality that may be used from several places, that's why we have the Utility classes. Nevertheless there are so many different elements (WebDriver, WebElement, Actions, ...) on JBehave and Selenium that you will want to extend and include more common functionality over them. So, even having only one, or two or a few of Utility classes may become classes with thousand of lines. 

Therefore, uses Proxy pattern always you can. For example, the WebElement is a pretty poor element from the WebDriver framework with only a few methods (click, submit and ...) and probably you will want to extend by including more methods such as focus, mouseover, mouseout, etc. Moreover, some of these methods may have a different implementation depending on the browser. So, includes a WebElementProxy class with these methods and a custom implementation of this proxy for each browser rather than having an utility class with these methods and an IF statement to include all browser behaviours.

5.- Scenarios organization and CacheLookup.

WebDriver is slow, too slow. Usually, navigation between pages is the more time consuming command due to web driver waits until the DOM page is fully loaded. So, let's organise your scenarios in the way that it requires as less number of navigations as be possible. I strongly suggest not having a script with scenarios regarding to an use case, but a script per setup. 

Example:
We have these three scenarios:

Stories about title use case
 Scenario A
 When I navigate to Home page
 Then I check the title

Stories about footer use case
 Scenario B
 When I navigate to Landing page
 Then I check the footer

 Scenario C
 When I navigate to Home page
 Then I check the footer

Instead of having stories by use cases, have stories by setup/preconditions:

Stories about Home page
 Setup 
 When I navigate to Home page
 
 Scenario A
 Then I check the title
 
 Scenario C
 Then I check the footer

Stories about Landing page
 Setup 
 When I navigate to Landing page

 Scenario B
 Then I check the footer
If you are organising your scripts of this way, then you will be able to use the CacheLookup annotation to persist some WebElements with confident because surely the WebElement will be displayed there and WebDriver will not need to retrieve a WebElement again and again.

6.- Don't use sleep.

You can find many features in WebDriver framework to wait until something happens in the site such as WebDriverWait, Explicit wait, Implicit Wait (see more here - http://docs.seleniumhq.org/docs/04_webdriver_advanced.jsp). So, don't use Thread.sleep because it forces to wait all the time you ask for and it is not pretty elegant.

7.- No catch-all steps

Sometimes I have seen some steps with a too generic language such as "then it has <something>". This step will catch everything after the "it has" and it should be avoided as much as possible. Moreover, this kind of step surely has a code behind similar to: if ("some phrase 1".equals(something)) { ...} else if ("some phrase 2".equals(something)) { ... } ... that all programmers should avoid.

8.- Story/Scenario context

In JBehave you are able to use examples to repeat a same scenario with many items and all steps within this scenario will be performed again and again. Therefore, it is interested to have a Story/Scenario Context that will keep information and data only in the story and scenario context. 

Example:
Scenario: This is a test scenario
When I navigate to the  page
Then there are 0 containers containing any of the words: title, footer, movie
Then there are 0 containers containing any of the words: superman, robocop, spiderman
Then there are 0 containers containing any of the words: scary, horror, comedy
Then there are 0 containers containing any of the words: dogs, pets, cats

Examples:

| page   |
| Home Page             |
| Landing Page          |

In the each Then statement, the code is requesting all containers in the current page and for each container, it is requesting again the content to check whether the specified words in the Then statement appear in that text. As a summary, each Then statement is calling to the WebDriver many times: one for retrieving all containers and one call for getting the content of each container. 

Also, you can have something similar using CacheLookup annotation but this context can be used in many other purposes like adding data from external data sources or similar.

9.- Code coverage in your scenarios

Another common pitfall that I have heard is that the project to automate the site does not need to be code covered because it would be a redundant thing: testing to test the test project. And I strongly disagree. You are coding a framework to automate a site and this framework does need to be code covered in order to ensure it.

10.- Project structure

Finally, when you have a lot of scenarios, steps and so on and you are coding using Eclipse and the JBehave plugin, you will feel that the IDE starts taking many freeze times. This is because the jBehave plugin detects that you are doing something in the project and the scripts need to be rebuilt. Maybe you don't have this issue because you have a good logical separation of your modules, but I have seen a single project with everything in there and it can be messy. 

The simplest and minimal structure for this kind of project would be:

jBehave:         Project with jBehave scripts, steps and runners
|
automation:     Project to automate the web site. 
|
framework:     Utilities, Proxies, Converters, ...

And that's it for now. 

No hay comentarios:

Publicar un comentario