18

I'm somewhat new to Selenium and I have a problem. Whenever I write a test case, that test case starts with logging in, and it's the same four steps every time. If I change the password for that account, I'll have to change it individually in every test case.

How can I break those login commands out to keep my tests more DRY?

2
  • 1
    What language are you coding in, Jason?
    – user246
    Oct 3, 2012 at 21:00
  • 2
    I agree with Phil about page objects, but something else that may be useful to you is my answer to this question: sqa.stackexchange.com/questions/3798/…
    – Sam Woods
    Oct 3, 2012 at 22:14

7 Answers 7

14

squeemish and Phil offer great advice.

To their excellent answers I would add: If you are using Selenium IDE to create tests by recording, stop doing that. Learn (soon) how to write tests from scratch, in the programming language of your choice. Use Selenium IDE (and other record-and-playback tools) only to explore and to try out ideas.

The reason for my advice is that record-and-playback tools have no way of knowing your intentions, or what makes a given UI gizmo interesting to you. All it knows is that you clicked on it or typed something into it. So it can never generate the kinds of meaningful abstractions that squeemish and Phil describe.

One way to learn how to write tests from scratch is to start with a test you've created via Selenium IDE. Notice what "code smell" is bugging you most about the test. Learn what refactoring to apply to remove the smell. Rinse and repeat until the test is expressive and maintainable. Then do it again with the next test.

Over time, you will learn what you want the tests to look like. Then you can use what you've learned to write good tests from scratch.

With people who are new to test automation, I tend to focus on three principles:

  • Remove duplication
  • Name every important idea
  • Hide incidental details

Here's a paper where I give an example of how to do this: http://dhemery.com/pdf/writing_maintainable_automated_acceptance_tests.pdf

For this example, I stayed within the tool (Robot Framework) rather than shifting to a more general programming language. But the principles are the same. And here's a nice video of Bob Martin applying the same principles using a different test tool (FitNesse): http://blog.objectmentor.com/articles/2009/12/07/writing-maintainable-automated-acceptance-tests

2
  • 1
    It's probably overkill to write every test entirely from scratch every time. Depending on your circumstances and tools, it may be sufficient to record a test, then to refine the living daylights out of it. In any case, +1.
    – user867
    Oct 4, 2012 at 0:38
  • 1
    That's a great way to learn to write the tests from scratch. Start with recording and modifying, then after a while you will be familiar with the language and expectations
    – squeemish
    Oct 4, 2012 at 13:44
6

Use an Abstract class. I have our entire login procedure in an abstract class and just call that method as the first thing in my test. If any credentials need to be changed, you only need to change them in one place.

The method in my abstract class:

protected void getBaseURLAndLoginTestUser() throws Exception
{
    driver.get(BASE_URL);

    assertEquals("some text here", findElementBySelector("h3").getText());

    WebElement userName = findElementById("j_username");
    userName.clear();
    userName.sendKeys("testing1");

    WebElement password = findElementById("j_password");
    password.clear();
    password.sendKeys("testing1");

    WebElement loginButton = findElementById("modalDialogButton");
    loginButton.click();
}

Which is then used in another method in my abstract:

protected void setup() throws Exception 
{
        getBaseURLAndLoginTestUser();

        WebElement selectRole =  findElementByXpath("//div[@id='selectRoles']/ul/li[text()='loginRole (Dept C)']");
        selectRole.click();

        Thread.sleep(2000);

        WebElement continueButton = findElementById("continue");
        continueButton.click();

        Thread.sleep(2500);

        assertEquals("some text here", findElementBySelector("header.container_12.clearfix > h1").getText());

        String destinationUrl = navigateToUrl(getNavigationUrl());
        driver.get(destinationUrl);
    }

And FINALLY, all you see in my test class is:

public class NameOfClass extends AbstractClass
{
    @Override
    @BeforeClass
    public void setup() throws Exception
    {
        super.setup();
    }
}

I have to use super because I change the URL I am navigating to, which is contained in another abstract class.

2
  • 1
    Thread sleep without checking any condition? Why 2500ms and not 2600 ms?
    – dzieciou
    Apr 11, 2013 at 19:08
  • the Thread sleep usage is spotty. The way our web app is built, the enabling/disabling of buttons and how to check them is very difficult. we haven't found a better way yet.
    – squeemish
    Apr 12, 2013 at 11:40
2

I can suggest you to use RobotFramework+SeleniumLibrary for testing website (here you can find a Demo http://code.google.com/p/robotframework-seleniumlibrary/wiki/Demo).

You can declare variables to use in your TCs, so if you change the password you don't need to change in every TCs

# global variables -- 
${user} username  
${password}  password

# tc --
...
Input Text   usr  ${user}
Input Password   pwd  ${password}
...

You can see more here:

1

To start with, what everyone else said.

I'd suggest that your first goal should be to learn how to split your test cases into reusable chunks so your login is a single test case which gets called by all the others.

The method I use (regardless of tool) is to define a test case as containing one or more test actions, each with one or more steps, so a test case of (for example) logging into a webstore and ordering an item breaks down to a set of actions like this:

  • Run targeted browser
  • Load entry URL
  • Log in as user
  • Select quantity x of item y
  • Select payment method
  • check confirmation page
  • exit browser

Each action can be used in any test case, so you only need the log in steps existing in one place.

I'll also typically have the actions take parameters so that I can reuse the action with different data. So the log in action would take in an identifier pointing to the user needed for the test case, and break down to steps something like this:

  • Retrieve user details for user login 'testuser1'
  • Enter user name
  • Enter user password
  • Click logon button
  • Verify successful action

(Of course, if you include success/failure information in your user details, you can call a "successful action" one that has the expected result whether it be a logged in user or an invalid password message).

I've found - by starting the way you did, and dealing with the ever-growing collection of identical or almost identical code - that modularizing your test cases this way helps to keep things clean and easily maintained.

1

What I do to get around this is to create a test case at the beginning of the test suite that sets all my common variables (test users, test company, build number, etc...) using the store command. Then I create a test case to log in that uses the variable from the common variables test case. The subsequent test cases also use the variables from the first test cases to run the tests. In fact, I can then create other test suites and add the common variables test case at the top so that all my test suites can use the same variables. This alone has saved me from a lot of headache.

For example you can have let's say a test case called commonvariables.html:

store|http://websitetobetested.com/|baseURL
store|[email protected]|username
store|mypassword|password

Then have another test case called login.html:

open|${baseURL}|
type|id=login_username_field|${username}
type|id=login_password_field|${password}
click|link=Sign In|
verifyTextExists|Welcome!|
click|link=Sign Out|
click|link=OK|

So this is just a simple example of a test suite that logs the user in then logs them off. From here on you can resuse the same test cases or even change the variables but use the same test cases in a different suites.

I hope this helps.

0

If you're using Selenium IDE, see SeLite and its add-on SelBlocks Global - https://code.google.com/p/selite/wiki/SelBlocksGlobal. It helps tests to be packaged, reusable, configurable (and therefore more maintainable). It improves test reuse through functions (shared across test cases), loops, and other code structures.

So, you could define a function in one test case, and call it from other test cases.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.