views:

38

answers:

1

We are implementing a web test automation project for some intranet applications.

To easy the writing of each test, we are designing a Java DSL that can be implemented using different adapters (we've chosen Sahi and Selenium/WebDriver so far, as we want to measure them side by side in terms of performance, readability, maintainability, etc.).

We've identified two types of operations in the DSL:

1) Primitive: its implementation will surely have to deal with HTML/Selenium/Sahi/etc specifics. Example: (using Sahi web driver)

public void insertProjectRecord(String projectName) {
  b.link("Create new project").click();
  b.textbox("ctl00$ProjectForm$Name").setValue(projectName);
  b.span("Insert").click();
}

2) Non-Primitive: an operation worth including in our DSL for reusability purposes, although that can be built using primitives. Example:

public void createFormulation(String projectName, String rteDummyText) {
  goToAddProjectPage();
  insertProjectRecord(projectName);
  switchToEditModeForFirstAvailableRecord();
  editBeneficiaryCountries();
  editAcronyms(rteDummyText);
  saveSectionChanges();
}

Question: we initially started with an interface with only primitive operations, but later we changed it to an abstract class in order to include the non-primitive methods (which a specific implementations are allowed to override, if needed). However, it doesn't feel "OK" to mix primitives and non-primitives, and the list of methods will certainly became very long.

What other approach would you suggest and/or explore?

+2  A: 

I would highly recommend using the Page Object Model. In this you create a class for each page and then abstract items away.

I wrote a blog post on writing maintainable tests here. You can see my blog post on the Page object model here

So your object could be like below.

public class Home
{
    private readonly ISelenium _selenium;

    /// <summary>
    /// Instantiates a new Home Page object. Pass in the Selenium object created in the test SetUp(). 
    /// When the object in instantiated it will navigate to the root
    /// </summary>
    /// <param name="selenium">Selenium Object created in the tests
    public Home(ISelenium selenium)
    {
        this._selenium = selenium;
        if (!selenium.GetTitle().Contains("home"))
        {
            selenium.Open("/");
        }
    }

    /// <summary>
    /// Navigates to Selenium Tutorials Page. Selenium object wll be passed through
    /// </summary>
    /// <returns>SeleniumTutorials representing the selenium_training.htm</returns>
    public SeleniumTutorials ClickSelenium()
    {
        _selenium.Click("link=selenium");
        _selenium.WaitForPageToLoad("30000");
        return new SeleniumTutorials(_selenium);
    }

    /// <summary>
    /// Click on the blog or blog year and then wait for the page to load
    /// </summary>
    /// <param name="year">blog or blog year
    /// <returns>Object representing /blog.* pages</returns>
    public Blog ClickBlogYear(string year)
    {
        _selenium.Click("link=" + year);
        _selenium.WaitForPageToLoad("30000");
        return new Blog(_selenium);
    }
    // Add more methods as you need them
}

public class SeleniumXPathTutorial
{
    private readonly ISelenium _selenium;

    public const string FirstInput = "number1";
    public const string SecondInput = "number2";
    public const string Total = "total";

    public SeleniumXPathTutorial(ISelenium selenium)
    {
        this._selenium = selenium;
    }

    public bool IsInputOnScreen(string locator)
    {
        return _selenium.IsElementPresent(locator);
    }
}

and then the test class would be like

[TestFixture]
public class SiteTests
{
    private ISelenium selenium;
    [SetUp]
    public void Setup()
    {
        selenium = new DefaultSelenium("localhost", 4444, "*chrome", "http://www.theautomatedtester.co.uk");
        selenium.Start();
    }

    [TearDown]
    public void Teardown()
    {
        selenium.Stop();
    }

    [Test]
    public void ShouldLoadHomeThenGoToXpathTutorial()
    {
        Home home = new Home(selenium);
        SeleniumTutorials seleniumTutorials = home.ClickSelenium();
        SeleniumXPathTutorial seleniumXPathTutorial = seleniumTutorials.ClickXpathTutorial();
        Assert.True(seleniumXPathTutorial.
                    IsInputOnScreen(SeleniumXPathTutorial.FirstInput));
        Assert.True(seleniumXPathTutorial
                    .IsInputOnScreen(SeleniumXPathTutorial.SecondInput));
        Assert.True(seleniumXPathTutorial
                    .IsInputOnScreen(SeleniumXPathTutorial.Total));
    }
}
AutomatedTester
Thanks. I've seen the Page Object Model pattern, and it is something we will certainly explore, more aligned with OOD. Nevertheless, we decided to start with a command/procedural approach (why? too long to explain here...), and we faced with the primitives/non-primitives mix issue. So far, I've identified more reasons to keep primitives and non-primitives separated, but we still need to play around a little.
Sebastian
Switched to POM. We were afraid of it because we expect big changes in the navigability to the app, but now it feels the best way to do it. We will deal with the changes later. Thanks you.
Sebastian
The idea of Page object Model is to make updating tests easier when navigation changes happen
AutomatedTester