Wednesday, January 26, 2011

PRACTICUM: Selenium 1.0 Testing Tools: Chapter 7: Creating Selenium Remote Control Tests

This is the seventh entry for the TESTHEAD PRACTICUM review of PACKT Publishing's book Selenium 1.0 Testing Tools Beginners Guide by David Burns. The emphasis on this type of review is the actual exercises and the practical applications of what we learn. Rather than reprint out the full listing of the exercises in the book (and there are a bunch of them, as that is the format of this particular title), much of these chapters are going to be a summary of the content, some walk-through of application, and my take on what I see and do.



Selenium 1.0 Testing Tools is full of examples, "try it yourself" instructions and explanations of what happened. The book’s cover states “Learn by doing: less theory, more results”. The focus will be less on the concepts and more on the actual implementation and methods suggested. Because this chapter requires so much code example and steps, to preserve the intention of not copying David's work too directly, I have included in Red Text those sections of the book that are verbatim or almost verbatim.



My apologies for the delay in getting this chapter up, but time got the better of me transitioning between jobs, and I had to put this on the back burner. But there was a more insidious reason... I plain and simple found myself stuck in this chapter! Why you may ask? Because what's printed in the book does not match the available environments for Selenium RC! To be fair, I'm using a slightly different environment compared to what David is using, and I had some challenges with it in the last chapter. Those challenges became greatly compounded this time around and I really had a hard time navigating around them. This is where not having a background in Java slowed me down considerably, and where I could not figure out why I couldn't get the code to compile or successfully run. However, this is a Practicum, and the whole point is to show what I learned from these experiments, and what I learned is that an environment that is slightly different from the examples can cause frustrations when trying to work through the examples.



Chapter 7: Creating Selenium Remote Control Tests



At this stage, Selenium Remote Control should be set up on your system and you should be able to run Selenium Remote Control to drive tests. This chapter focuses on converting Selenium IDE tests to code (specifically Java, since that is what was used in the book as the example).

Making these conversions will add all of the flexibility of the Java programming language with the structure and functionality of Selenium. This chapter will cover:

- Converting Selenium IDE tests to run in a programming language and getting them running
- Writing Selenium Remote Control tests from scratch
- Applying best practices such as Page Object design pattern to create lasting tests
- Running tests against a continuous integration server

Prerequisites:

This chapter expects the user to have the following tools installed to perform the exercises in this chapter:


Java IDE: IDEA Intellij (http://www.jetbrains.com/idea/download/)
Unit Testing Framework: jUnit (http://github.com/KentBeck/junit/downloads)


Converting Selenium IDE Tests to a Programming Language


We have so far focused on creating tests with the Selenium IDE. Now we will see what it takes to convert a Selenium IDE test case to run as a Java test case and use jUnit to drive the tests.

1. Open IntelliJ IDEA and create a new project.
2. Create a folder at the root of the project called test.
3. Click on File | Project structure.
4. Click on Modules on the left-hand side of the dialog that has loaded.
5. Click on the test folder that you created in the folder tree on the right-hand side of the dialog.
6. Click on the Test Sources button and the test folder should turn green.
7. Open the Selenium IDE.
8. Open a Selenium IDE test you saved as HTML, or quickly create a new one.
9. Click on File then move the mouse pointer down to Export Test Case As and then click on Java (actually there is no Java option, but there are two jUnit options. Choosing jUnit 3 matches the structure of the code example shown in the book). 
10. If following the book and looking at the example code in the book, change the text that says change-this-to-the-site-you-are-testing to book.theautomatedtester.co.uk.
11. Click on File | Project structure.
12. Click on Global libraries.
13. Click on the + to add a New Global Library.
14. Click on Attach Classes and add selenium.jar and common.jar. This should be in the same place as your Selenium-Server.jar (from my vantage point, these files are not here; they are not part of the distribution of Selenium RC 1.0.3 or of the previous version).
15. Do the same for jUnit now. You can create a new Global library for it or add it to the Selenium Global Library.
16. Click on the Modules link on the left-hand side again.
17. Click on the Dependencies tab.
18. Click on Add and click on Global Libraries. Add the Selenium and jUnit libraries.
19. Click on Apply. When this is done the text selenium should turn purple.
20. We are now ready to run Selenium Server. We do this by running java–jar selenium-server.jar.
21. Right-click on the Java file created by Selenium IDE and click on "Run testcase1".

Below is an example of the text that was captured from the server when I ran the test:

16:59:57.933 INFO - Command request: getNewBrowserSession[*chrome, http://book.theautomatedtester.co.uk/, ] on session null
16:59:57.939 INFO - creating new remote session
16:59:57.942 INFO - Allocated session 995fbbeba27c4f4eaa807f4f2eb9ad50 for http://book.theautomatedtester.co.uk/, launching...
16:59:58.001 INFO - Preparing Firefox profile...
17:00:01.392 INFO - Launching Firefox...
17:00:06.962 INFO - Got result: OK,995fbbeba27c4f4eaa807f4f2eb9ad50 on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:06.975 INFO - Command request: open[/chapter1, ] on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.739 INFO - Got result: OK on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.745 INFO - Command request: select[selecttype, label=Selenium RC] on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.784 INFO - Got result: OK on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.788 INFO - Command request: isTextPresent[Assert that this text is on the page, ] on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.817 INFO - Got result: OK,true on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.822 INFO - Command request: isTextPresent[Home Page, ] on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.849 INFO - Got result: OK,true on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.853 INFO - Command request: click[link=Home Page, ] on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.900 INFO - Got result: OK on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:08.906 INFO - Command request: waitForPageToLoad[30000, ] on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:09.325 INFO - Got result: OK on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:09.330 INFO - Command request: testComplete[, ] on session 995fbbeba27c4f4eaa807f4f2eb9ad50
17:00:09.330 INFO - Killing Firefox...
17:00:09.445 INFO - Got result: OK on session 995fbbeba27c4f4eaa807f4f2eb9ad50

For the record, as I mention above, the book and what was possible to do veered away from what I was able to do, as two of the libraries mentioned did not exist. So what did I ultimately do? I went into the server folder and linked the jar for selenium-server, the jars in the Java client component of Selenium RC and the library and class structure of jUnit. Even with that, I am still seeing unusual behavior in that the Selenium RC printout is only shown for a couple of seconds, then disappears.

Creating a Selenium instance with JUnit 3

This next section walks the user through how to create a test for JUnit3 style syntax. The focus is to get the user away from running the IDE to capture tests, convert them to Java, and then massage and run them.

1. Create a new java class in IDEA.


2. Add the Selenium Import to your java class:


import com.thoughtworks.selenium.*;


3. For JUnit 3, we need to extend our java class with the TestCase class.


public class SeleniumBeginnersJUnit3 extends TestCase {
Selenium selenium;
}


4. We now need to set up a new Selenium instance. We will do this in the setUp method that is run before the tests. In this we will initialize the selenium object that we previously created. This object takes four parameters in the constructor. They are:


- Machine name hosting the Selenium Remote Control server.
- Port that Selenium Remote Control server is running.
- Browser string. For example *chrome.
- Site under test.


The following code shows the initialization:
selenium = DefaultSelenium("localhost",4444,"*chrome",http://book.theautomatedtester.co.uk);


5. Let's start the browser up programmatically. We do this by calling the start() method.


selenium.start();


Your setUp() method should look like the following snippet:


public void setUp(){
selenium = new DefaultSelenium("localhost",4444,"*chrome","http://book.theautomatedtester.co.uk");
selenium.start();
}


6. Now we need to create a test method. We do this by creating a new method that has test as a prefix. For example: testShouldDoSomething(){…}.


7. We can add selenium commands in our test so that it can drive the browser. For example:


public void testShouldOpenChapter2LinkAndVerifyAButton(){
selenium.open("/");
selenium.click("link=Chapter2");
selenium.waitForPageToLoad("30000");
Assert.assertTrue(selenium.isElementPresent("but1"));
}


8. Right-click on the test method and click on Run Test. You should see your test driving the browser.


The example below comes from the exercise. Note: the program below is described through the steps and is the result through the description. For a beginner, this would be the code structure and syntax as described. The code as described, however, generates errors when we attempt to run it (specifically, we need to add another import statement so that the assert command would be recognized). Minor point and the compiler catches this, but as this is a beginner's guide, a full and complete printout of the resulting program would be helpful to see if there are any discrepancies or structural differences.

import com.thoughtworks.selenium.*;

public class SeleniumBeginnersJUnit3 extends TestCase {
 Selenium selenium;
}

public void setUp(){
 selenium = new DefaultSelenium("localhost",4444,"*firefox","http://book.theautomatedtester.co.uk");
 selenium.start();
}

public void testShouldOpenChapter2LinkAndVerifyAButton(){
 selenium.open("/");
 selenium.click("link=Chapter2");
 selenium.waitForPageToLoad("30000");
 Assert.assertTrue(selenium.isElementPresent("but1"));
}

Creating a Selenium instance with SeleneseTestCase setUp()

In the previous section we explicitly set the properties to start a browser. Selenium provides a command called SeleneseTestCase that uses a different format. The steps are as follows:

1. Create a new java class in IDEA.


2. Add the Selenium Import to your java class.


import com.thoughtworks.selenium.*;


3. For JUnit 3, we need to extend our java class with the TestCase class. Selenium has its own version of the TestClass called SeleneseTestCase.
public class SeleniumBeginnersJUnit3 extends SeleneseTestCase {
}


4. We now need to set up a new Selenium instance. We will do this in the setUp method that is run before the tests. In this we will initialize the selenium object that we created previously. This method takes two parameters. These are:


- Browser string. For example *chrome.
- Site under test.


The following code shows the initialization:


setUp("*chrome",http://book.theautomatedtester.co.uk);


5. Now we need to create a test method. We do this by creating a new method that has test as a prefix. For example: testShouldDoSomething(){…}.


6. We can add selenium commands in our test so that it can drive the browser. For example:
public void testShouldOpenChapter2LinkAndVerifyAButton(){
selenium.open("/");
selenium.click("link=Chapter2");
selenium.waitForPageToLoad("30000");
Assert.assertTrue(selenium.isElementPresent("but1"));
}


7. Right-click on the test method and click on Run Test. You should see your test driving the browser.

I saw errors when I tried to run this example. Again, I'm not trying to be too critical here; I have a different environment and some things are just not lining up, and I can accept that, but it really drives home the point that, in this books examples, Ubuntu Linux is used as the development platform, and the differences and the errors are different, and may take awhile to resolve (or in my case, yet to resolve).


Creating a Selenium instance with JUnit 4


The following steps show how to create a new test with JUnit 4 syntax:


1. Create a new java class in IDEA.


2. Import the Selenium and JUnit. You can the use the following code:
import com.thoughtworks.selenium.*;
import org.junit.*;


3. We now need to start a browser. You will need to declare a Selenium variable. Do this outside of any method.


4. Create a new method that will be run before any of the tests. Inside that we will start our selenium instance. Your code will look similar to the following:
@Before
public void setUp(){
selenium = new DefaultSelenium("localhost",4444,"*chrome",
"http://book.theautomatedtester.co.uk");
selenium.start();
}


The .start() call will make Selenium start the browser up


5. Now that we can start up the browser, we will also need to kill it when
our test has finished. We do this by creating a @After method with
selenium.stop() in it. The method will look similar to the following:


@After
public void tearDown(){
selenium.stop();
}


Your test file should look like this now:

import com.thoughtworks.selenium.*;
import org.junit.*;

public class Selenium2 {
  Selenium selenium;

  @Before
  public void setUp(){
    selenium = new DefaultSelenium("localhost",4444,"*chrome","http://book.theautomatedtester.co.uk");
    selenium.start();
  }

  @Test
  public void shouldOpenChapter2LinkAndVerifyAButton(){
  }
  
  @After
  public void tearDown(){
    selenium.stop();
  }
}


6. Run the test by right-clicking and clicking on Run Test in the context menu.

What's good about this example is that a complete test was displayed for comparison's sake.


Creating a Selenium instance with TestNG


TestNG is another popular testing framework for Java. It can be more extensible than JUnit.

1. Create a new class file for testing against http://book.theautomatedtester.co.uk/.


2. Create a new setUp method. Use the annotation @BeforeMethod. This will need to have the code to start a Selenium instance.


3. Create a new tearDown method. Use the annotation @AfterMethod. This will need to have the code to close a Selenium Instance.


4. Create a new test. This uses the same annotation as JUnit 4.


5. When you have completed that your test file should look like this:


package uk.co.theautomatedtester.book;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import org.testng.annotations.*;

public class Chapter10 {
  Selenium sel;
  @BeforeMethod(alwaysRun=true)
  public void setUp(){
    sel = new DefaultSelenium("localhost",4444,browser,"http://book.theautomatedtester.co.uk");
    sel.start();
  }
  
  @Test
  public void testShouldOpenTheRootOfSite(){
    sel.open("/");
  }

  @AfterMethod
  public void tearDown(){
    sel.stop();
  }
}


Creating a Test From Scratch

With the previous examples, the setup has been made so that the tests can be added. Using the last example, now we create a test that loads the root of the site, moves to the chapter2 page, and then verifies a button is on the page.


1. Create a new method. I have called mine shouldOpenChapter2LinkAndVerifyAButton. Add the @Test attribute to that method.


2. Use the open() function and open the root (/) of the site.


3. Click on the link for Chapter2.


4. You will need to call the waitForPageToLoad() method to wait for it to load. This method takes one parameter and that is how long Selenium should wait for it to load before throwing an error.


5. Finally Assert that the button but1 is on the screen. This will need to use JUnit's Assert class. You will need to call Selenium's isElementPresent call and wrap that with assertTrue. When your test is complete it should appear as follows:


@Test
public void shouldOpenChapter2LinkAndVerifyAButton(){
  selenium.open("/");
  selenium.click("link=Chapter2");
  selenium.waitForPageToLoad("30000");
  Assert.assertTrue(selenium.isElementPresent("but1"));
}


Selenium Remote Control Best Practises


David makes some examples in this section to describe some best practices to use so that tests can be made to be more maintainable and easier to update. the following example creates a DSL so that people can see what is going on.


The example used is were a number of tests work on a site that requires logging in and navigating to a certain page. the approach would be to find out if you are on the correct page and if not, go there.


1. Create a new java class in IDEA.


2. Import the relevant Selenium Packages.


3. Create the setUp() and tearDown() method. I prefer the JUnit 4
style of tests and so will use code samples with the annotations.


4. We need to check that the test is on the correct page. For this we will use the selenium.getTitle to see that page title, and then if incorrect move to the chapter2 link. We do this because navigating to page is slower than checking the page's title or any other calls to the page already loaded.


5. We need to then validate that it is correct and then work accordingly. The following code snippet is an example of how we can do this:


if (!"Page 2".equals(selenium.getTitle())){
  selenium.open("/chapter2");
  selenium.waitForPageToLoad("30000");
}


6. Create the rest of the test to check that items are on the page.


Moving Selenium Steps Into Private Methods to Make Tests Maintainable


The following example walks the user through the process of refactoring code so that the tests will be easier to use for multiple executions.

Let us create a number of tests as follows:
@Test
public void shouldCheckButtonOnChapter2Page(){
  selenium.open("/");
  selenium.click("link=Chapter2");
  selenium.waitForPageToLoad("30000");
  Assert.assertTrue(selenium.isElementPresent("but1"));
}

@Test
public void shouldCheckAnotherButtonOnChapter2Page(){
  selenium.open("/");
  selenium.click("link=Chapter2");
  selenium.waitForPageToLoad("30000");
  Assert.assertTrue(selenium.isElementPresent("verifybutton"));
}


Using the given examples let's break these down.


1. Both examples always open the root of the site. Let's move that into its own
private method. To do this in IDEA you highlight the lines you want to refactor
and then right-click. Use the context menu and then Extract Method.


2. Then you will see a dialog asking you to give the method a name. Give it something
meaningful for the test. I have called it loadHomePage.


3. Now do the same for the other parts of the test so that it looks a lot more succinct.


4. Your test class should look something like this:


@Test
public void shouldCheckButtonOnChapter2Page(){
  loadHomePage();
  clickAndLoadChapter2();
  Assert.assertTrue(selenium.isElementPresent("but1"));
}

@Test
public void shouldCheckAnotherButtonOnChapter2Page(){
  loadHomePage();
  clickAndLoadChapter2();
  Assert.assertTrue(selenium.isElementPresent("verifybutton"));
}

private void loadHomePage() {
  selenium.open("/");
}

private void clickAndLoadChapter2() {
  selenium.click("link=Chapter2");
  selenium.waitForPageToLoad("30000");
}

There's a lot of meat to this chapter, and if you are not a really avid coder, or if java isn't something you are very familiar with, there can be some rough going in spots. also, as I stated at the beginning, having a different environment or not having access to the same options described can make for results that veer from the expected. I found this chapter to be frustrating to work through because some tests worked and some didn't. Overall, I felt the explanations were well done, but you may need to take my approach and explore to see what options are actually available in your implementation and make the necessary changes. Practice with the code and the implementation that is most familiar to you and see how/what works.

1 comment:

Veretax said...

When I use selenium, I set up a folder in my root, and one within my project so that I could launch it from inside of Visual Studio. By doing that I can capture those messages from the server console a lot easier. Not sure if that makes difference given what you were trying to do though.