Selenium Best Practices

It’s a summary (and few extras) of test_design_considerations

Use PageObjects pattern
Be fluent with 
  - return this, varargs, generics, 
  - reuse your model and jodatime
Be robust and portable 
  - Prefered selector order : id > name > css > xpath 
  - Avoid Thread.sleep prefer Wait or FluentWait
  - Use relative URLs
  - Don’t rely on specific Driver implementation
  - Create your dataset
Know your new tool
  - Keep up to date (versions and usage pattern)
  - Troubleshooting 
      - jre 1.6
      - IE (zoom, Protected mode setting )
      - Firefox/firebug startpage
  - How to deal with UI components like... fileupload, datepicker, ajaxtables,...
  - Detect when selenium isn't the good tool for the job
  - Don't be afraid to hack around selenium

Use PageObjects pattern

Page Object is a Design Pattern which has become popular in test automation for enhancing test maintenance and reducing code duplication. A page object is an object-oriented class that serves as an interface to a page of your Application Under Test. The tests then use the methods of this page object class whenever they need to interact with that page of the UI. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently all changes to support that new UI are located in one place.

The Page Object Design Pattern provides the following advantages :

  • There is a clean separation between test code and page specific code such as locators (or their use if you’re using a UI map) and layout.
  • There is  a single repository for the services or operations offered by the page rather than having these services scattered through out the tests.

For example you have a LoginPage that you can reuse in all your integration test.
if the logon has now a new option or the layout changed a bit… adapt the LoginPage… that’s it !

More on page object : page-objects-in-selenium-2-, PageObjectFactory, simple example based on google search page

Be fluent !

With return this

It makes often your tests more readable if your “void” methods return “this”.


   public class CampaignCreatePage extends AbstractPage {
       ...
    public CampaignCreatePage withCampaignName(String campaignName) {
        name.sendKeys(campaignName);
        return this;
    }

So you can chain the calls like :

    @Test
    public void shouldntSaveWithoutAnyErrors() throws Exception {
        CampaignCreatePage createPage = openCampaignCreatePage();
        createPage.hasNumberOfErrors(4);
        createPage.withCampaignName("SELENIUM").save();
        createPage.hasNumberOfErrors(3);
        createPage.withAgent("firefox").save();
        createPage.hasNumberOfErrors(2);
        createPage.withAssignee("tom").save();
        createPage.hasNumberOfErrors(1);
    }

With varargs

You can also use varargs to make the api cleaner

    public CampaignCreatePage withCampaignTargetsIds(long... targets) {
        targetsIds.sendKeys(StringUtils.join(target,","));
        return this;
        }

In the test this give us something like

    @Test
    public void shouldntSaveTargetUnknownIds() throws Exception {
        CampaignCreatePage createPage = openCampaignCreatePage();
        createPage.withCampaignName("SELENIUM").withAgent("firefox").withAssignee("tom").withProduct(0).withCampaignTargetsIds(1, 2, 3, 4);
        createPage.save().hasNumberOfErrors(1);
    }

With generics

If you have a component that don’t have access to your pageObject. For example, a generic menu where plugins can contribute links to their own pages.
you can use this trick :

    public  T clickOnMenu(String path, Class pageClassToProxy) {
        clickOnMenu(path);
        return PageFactory.initElements(getDriver(), pageClassToProxy);
    }

 

    @Test
    public void testBrodocWithoutMemberOrProspectIdentified() {
        processLogon();
        MemberIdentificationPage page = openMemberIdentificationPage();
        page = page.getMenuElement().clickOnMenu(LuceneSearchPage.URL, LuceneSearchPage.class);
        page.hasInfoMessage();
        page.withNameFilter("mestachs").search().getResults().hasResults()
 }

By reusing your model and joda

For example the CampaignSearchPage can reuse the SearchCampaignParameter

...
     public CampaignSearchPage searchFor(SearchCampaignParameter parameter) {
        clearForm();
        selectRadio("campaignTargetType", parameter.getPersonType());
        sendString(campaignName, parameter.getCampaignName());
        sendDate(creationDateFrom, parameter.getCreationDateFrom());
        sendDate(creationDateTo, parameter.getCreationDateTo());
        sendDate(campaignDateFrom, parameter.getCampaignDateFrom());
        sendDate(campaignDateTo, parameter.getCampaignDateTo());
        findCampaigns.click();
    }
...

Be robust and portable

Preferred selector order : id > name > css > xpath

To locate an element we can use

  • the element’s ID
  • the element’s name attribute
  • an XPath statement
  • by a links text
  • document object model (DOM)

In practice, you will discover

  • id and name are often the easiest and sure way.
  • xpath are often brittle. for example you have 3 tables displayed but sometimes there are no data and the table isn’t rendered, your xpath locating the second table will not always return the intented one.
  • css are the way to go in conjunction of id and name !
  • locating links in an internationalized application is sometimes hard… try to use partial href.

various selectors articles : why-jquery-in-selenium-css-locators-is-the-way-to-go, css selector demystified, jquery selectors

Avoid Thread.sleep prefer Wait or FluentWait

Instead of sleep

    public void clickOnContactType() {
        getDriver().findElement(By.id("contacttypelink")).click();
        sleep(500);
    }

Prefer this fluentWait

    public void clickOnContactType() {
        getDriver().findElement(By.id("contacttypelink")).click();
        SeleniumUtil.fluentWait(By.name("handle"), getDriver());
    }

It’s more robust, deterministic, in case of element not found… the exception will be clearer.

Another alternative is

(new WebDriverWait(driver, 30)).until(new ExpectedCondition() {
                public Boolean apply(WebDriver d) {
                    return d.getTitle().toLowerCase().startsWith("java developer");
                }

More on this http://www.thoughtworks-studios.com/twist-agile-test-automation/2.3/help/how_do_i_handle_ajax_in_selenium2.html

Use relative URLs

Avoid hardcoding hosts

getDriver().get("http://nl.wikipedia.org/wiki/Wiki");
WikiMainPage page = PageFactory.initElements(getDriver(), WikiMainPage.class);
page.search("sdfsdf");

Prefer configuration and pass relative urls like

openPartialUrl("/");

That use an abstract method


    protected void openPartialUrl(String partialurl) {
        getDriver().get(getUrlPrefix() + partialurl + "?siteLanguage=" + this.settings.getLanguage());
    }
    private static String getPrefix() {
        return StringUtils.replace(System.getProperty("selenium.website.url", HTTP_PREFIX), "@localhost@", getCanonicalHostName());
    }
    private static String getCanonicalHostName() {
        try {
            return java.net.InetAddress.getLocalHost().getCanonicalHostName();
        } catch (Exception e) {
            return "127.0.0.1";
        }
    }

It is easy then to launch these tests against another server (integration,acceptance,… or jetty in integration tests)

Don’t rely on specific Driver implementation

Don’t assume that driver will be an instance of FireFoxDriver or InternetExplorerDriver. For example when running the integration tests on your continuous build (linux) you will receive a RemoteDriver.

It’s quite easy to create a small framework around selenium using :

  • LabelledParameterized : to be able to run with different browsers IE,FF,… or with different user language fr/nl/en
  • ScreenShotWatchMan : that takes screenshot on exception and logs the html sources. (see ./target/screenshot/*.png)

Create your dataset

Avoid something like assuming that the data is always there.

CampaignConsultPage consult = openCampaignConsultPage(2132103215L);
CampaignEditPage edit = consult.goToEditPage();
consult = edit.save();
consult.hasInfoMessage();

Create a new campaign and check consult

CampaignCreatePage createPage = openCampaignCreatePage();
createPage.withCampaignName("SELENIUM" + new DateTime()).withAgent("tom").withAssignee("tom").withProduct(0);
createPage.save().hasNumberOfErrors(0);
createPage.hasInfoMessage();
Long campaignId = createPage.getCampaignId();
CampaignConsultPage consult = openCampaignConsultPage(campaignId);
CampaignEditPage edit = consult.goToEditPage();
consult = edit.save();
consult.hasInfoMessage();

Know your new tool

Keep up to date

Keep your binaries and knowledge up to date !
This official blog contains announcements and also some a lot of links to possible application of selenium
StackOverflow forums are really good to learn from other mistakes. The official Sauce Labs Blog is also really interesting for their real life experiences.

Use jre 1.6

If while starting your integration tests you encounter :

java.lang.NoSuchFieldError: HOURS
at org.openqa.selenium.remote.internal.HttpClientFactory.(HttpClientFactory.java:44)

or

java.lang.NoSuchFieldError: java/util/concurrent/TimeUnit.HOURS

Run your test with a jre 1.6

How to close Firebug startpage

FirefoxProfile profile = new FirefoxProfile();
profile.setPreference("extensions.firebug.currentVersion", "1.8.2");
driver = new FirefoxDriver(profile);

note that you may need to adapt the version.

IE not working

check

  • The browser zoom level must be set to 100% so that the native mouse events can be set to the correct coordinates.
  • On IE 7 or higher on Windows Vista or Windows 7, you must set the Protected Mode settings for each zone to be the same value. The value can be on or off, as long as it is the same for every zone. To set the Protected Mode settings, choose “Internet Options…” from the Tools menu, and click on the Security tab. For each zone, there will be a check box at the bottom of the tab labeled “Enable Protected Mode”.

Possible workaround for Protected Mode

DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();
ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
ieCapabilities.setCapability("ensureCleanSession", true);
driver = new InternetExplorerDriver(ieCapabilities);</pre>

How to deal with ui elements like

File upload

Selenium Tips: Uploading Files in Remote WebDriver

Single and multi select

One option

public class AbstractPage {
...
   protected void selectRadio(String name, String code) {
    WebElement select = this.webDriver.findElement(By.xpath("//input[@value='" + code+ "'and @name='" + name + "' ]"));
    select.click();
   }
...

Maybe a better option is using org.openqa.selenium.support.ui.Select

    Select singleSelection = new Select(getDriver().findElement(By.id("single-selection")));
    singleSelection.selectByVisibleText("July");
    Select mulitpleSelection = new Select(getDriver().findElement(By.xpath("//select[@multiple='multiple' and @size=12]")));
    mulitpleSelection.deselectAll();
    // Select February, August and November using different functions.
    mulitpleSelection.selectByIndex(1); // February
    mulitpleSelection.selectByValue("Aug"); // Select ...
    mulitpleSelection.selectByVisibleText("November"); // Select November

DatePicker

public class CampaignSearchPage extends AbstractPage {
  ...
    sendDate(creationDateFrom, parameter.getCreationDateFrom());
  ...</pre>

sendDate is defined in AbstractPage

   protected void sendDate(WebElement e, DateMidnight dm) {
    if (dm != null) {
        sendString(e, dm.toString("dd/MM/YYYY"));
    }
   }

AjaxTableElement

If you use table elemenst with paging, sorting,… you can perhaps create a component AjaxTableElement
add in pageObject :

public class CampaignSearchPage extends AbstractPage {
          ....
         public AjaxTableElement getTableResult() {
        return new AjaxTableElement(getDriver(),"results");
    }
}

Then you can use the AjaxTableElement

page.searchFor(param);
page.getTableResult().hasResult();
page.getTableResult().clickOnResult(1);

Detect when selenium isn’t the good tool for the job

Selenium can’t deal with OS level dialogs so first avoid them as much as possible…
For eg don’t try to download a pdf through selenium and open acrobat reader.
You may be tempted to use tool like : autoit or sikuli (his java api) but you will loose portability and may be they will only offer you a false sense of quality.

Don’t be afraid to hack around selenium

A lot of people are using it but also trying new stuff like :
– collecting performance statistics.
– creating a full acceptance framework
– implementing a new webdriver

, ,

  1. #1 by mestachs on August 13, 2012 - 7:26 pm

    The one vitamin experts say really is worth taking. “Selenium”

  2. #2 by Mathew Bukowicz on August 14, 2012 - 7:00 am

    Very good and thorough article. Thanks!

  3. #3 by CostaRica506 on August 15, 2012 - 11:33 am

    Great Article loaded with excellent information.

  4. #5 by Atin Chak on August 21, 2012 - 7:29 am

    Hi,

    Can anyone please suggest me how can I check JS script error using selenium. i have tried http://www.silverwareconsulting.com/index.cfm/2010/6/7/Checking-for-JavaScript-Errors-with-Selenium approach but it did not work.

    Please suggest me regarding this.

    Thanks,
    Atin

  5. #9 by mestachs on August 25, 2012 - 6:38 pm

    about INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS
    http://jimevansmusic.blogspot.ca/2012/08/youre-doing-it-wrong-protected-mode-and.html

  6. #10 by abodeqa on September 27, 2012 - 10:58 am

    http://wp.me/p2nDO1-5i
    I have made one effort to learn and make other to learn Xpath with few basic examples

  7. #11 by Florian on January 11, 2013 - 8:41 am

    Good article, very useful, thanks.

    Just a typo:

    return d.getTitle().toLowerCase().startsWith("Java Developer");

    This has “good” chances to always return “false” 🙂

  8. #13 by Sebastian on January 29, 2013 - 8:36 pm

    Great article, thank you so much.

  9. #14 by Juan Mendes on April 2, 2013 - 7:13 pm

    ID is the easy way, but it’s also the cheap way out. If you code with IDs, you can never have two elements on the same page. Using class names gives you the same concept, but you can have multiple elements on the page

    • #15 by mestachs on April 4, 2013 - 6:33 am

      Most framework encourage id/name for form/request binding so it fits “natural” to reuse them. Css Classes in my experience are more there to route the locator in the correct area of the page in case of non-unique name. And for xpath I avoid them like the pest.

  10. #16 by Gbele on July 2, 2013 - 7:27 pm

    Great article..Thanks!!!

  11. #17 by J. Morio Sakaguchi on August 30, 2013 - 6:41 pm

    This is the best article on Selenium and Page Objects I’ve come across. I’d love to see the full source code for this example and I was wondering what the save() method does.
    Great work!

  12. #18 by Georgios Kogketsof on October 6, 2013 - 10:43 am

    An excellent post very informative.

  13. #19 by autumnator on February 11, 2014 - 2:54 am

    For location strategy, always best to have explicit IDs for every element so you can map things out easily, where possilble, provided your developers were kind enough to help the testing team on this.

    But I would not rule out XPath, and in some cases, it works much better than CSS (albeit potentially slower) because of the ability to navigate around axis, up parent DOM tree, to previous/next siblings and match by text in a node (not within attribute). These are very powerful to use in mapping out a set of locators relative to another reference base locator. The only way to do this without XPath is to find the reference base element then from that element continue with find element down the DOM tree there. But then it is a pain to navigate back up or to siblings from there (without XPath or CSS).

    Overall, this is a great post. It would be nice if there’s a follow up post to present some more complex example of Selenium page objects rather than the simple one mentioned here.

    • #20 by mestachs on February 11, 2014 - 7:27 am

      Using IDs is great… that’s why I advocate that the developer should implement the test as they code, in order to get the IDs set and maintained.
      Letting another testing team code more advanced test/use case can be an option.

      I got bitten by the Xpath a few times (testing a screen that I don’t know by heart, with hidden sections).

      For the follow up, I’m no more using selenium daily so it’s a bit hard to make it relevant. Feel free to write yours, I will point your article.

      Thanks

  14. #21 by thefosslover on March 11, 2014 - 9:24 am

    Reblogged this on The Automation Tester.

  15. #22 by mestachs on March 18, 2014 - 7:17 pm

  16. #23 by Burdette Lamar on April 12, 2014 - 1:24 pm

    Thanks for this post. I’ve written about DRY page objects (http://burdettelamar.wordpress.com/2014/03/21/keep-your-page-objects-dry/) and DRY verifiers (http://burdettelamar.wordpress.com/2014/03/21/keep-your-page-objects-dry/) over at my blog.

  17. #24 by Manjula Subramanyam on September 17, 2015 - 9:56 am

    Hi. Most of my code is hard coded. Say for example,
    driver.findElement(By.xpath(“//form[@id=’form’]/div[3]/div/div/input”)).sendKeys(rdr.readData(path, “Sheet1”, 1, 0));
    driver.findElement(By.xpath(“//form[@id=’form’]/div[3]/div[2]/div/input”)).sendKeys(rdr.readData(path, “Sheet1”, 1, 1));
    driver.findElement(By.xpath(“//form[@id=’form’]/div[3]/div[3]/div/input”)).sendKeys(rdr.readData(path, “Sheet1”, 1, 2));

    How do I avoid it? any other ways? TIA.

    • #25 by mestachs on September 17, 2015 - 7:41 pm

      1. create a page object

      class ProductFormPage extends PageObject {

      @FindBy(xpath = "//form[@id=’form’]/div[3]/div/div/input")
      WebElement productField;

      @FindBy(xpath = "//form[@id=’form’]/div[3]/div[2]/div/input")
      WebElement dateField;

      @FindBy(xpath = "//form[@id=’form’]/div[3]/div[3]/div/input")
      WebElement categoryField;

      public MyFormPage withCategory(String category) {
      categoryField.sendKeys(category);
      return this;
      }
      ... // idem for other fields

      2. extract your “xls sheet reader” to more meaningful names


      List getProductSearches(Reader rdr);

      3. combine everything


      for (ProductSearch productSearch : getProductSearches(Reader rdr)) ^
      productFormPage.withCompany(productSearch.getCategory()).withXXX(...).search().shouldDisplay("....")
      }

      4. Improve

      try to find better selector than the xpath : based on input name, id, css ?
      modify the app to add an attribute on the fields like data-purpose=”product-category” use it as selector “[data-purpose=’product-category’]”:
      refer for sample : http://blog.pixarea.com/2013/02/guidelines-for-manageable-html-slash-css-slash-js-in-a-well-tested-rails-app/
      to learn to use selectors, follow one of the codeschool online training : http://try.jquery.com/levels/2/challenges/1

      5. get inspired by : http://wakaleo.com/blog/selenium-2-webdriver-quick-tips-page-object-navigation-strategies

  18. #26 by mestachs on November 14, 2015 - 10:47 am

    • #27 by Manjula S on December 31, 2018 - 1:17 pm

      Wow! Awesome!. Sorry I forgot to reply as I was completely involved in implementing your code. I just checked. I remember it had worked 🙂

  19. #28 by Nitin Vinocha on July 28, 2016 - 11:52 am

    React JS is creating long ids. So still you suggest to go by ID selector or some other thing in that case

    • #29 by mestachs on March 22, 2017 - 12:47 pm

      I would really more on classes

  20. #30 by Marco on April 27, 2018 - 7:47 am

    Very nice article. The only thing I do not like is code this:
    PageObject.method().method().method();
    It’s very difficult to maintain if there are any changes to be made.

    • #31 by autumnator on May 30, 2018 - 4:43 am

      Well, I guess in that case you can break up the chain into multiple statements instead of a long chained call, sicne each method returns an object that has more methods you can execute.

      DataType obj1 = PageObject.method();
      DataType obj2 = obj1.method();
      obj2.method();

      you can obviously name them more representative of what the object & method is doing.

      That chained method technique isn’t actually bad to use, provided the sequence you implement doesn’t change much or is easy to tweak if it does change. It’s up to the user to decide if they want to call it that way or not. It can save several lines of code that way.

      But this only applies to standard Java and .NET, but in some frameworks or languages like Scala among others, it becomes more of a de facto way of programming I think and harder to decouple into separate lines. But with basic Java/.NET, you’re not forced to do so.

  1. A Smattering of Selenium #111 « Official Selenium Blog
  2. Selenium Best Practices - Testing Excellence
  3. Selenium Best Practices | Java 6 EE Testing | S...
  4. Selenium Best Practices | Estándares de ...
  5. Selenium Best Practices | Automated testing | S...
  6. Selenium Web Driver Environment Setup With J UNIT 4 and eclipse IDE | testingtoolsandtechniques
  7. ICS 613 – Final Project – Milestone 3 | Never A Dull Moment
  8. Test Automation with Selenium – Education Centre
  9. Best Practices Links – Automation Testing
  10. Selenium Automation- Best Practices – Software QA Automation

Leave a comment