jbauguess
Tera Expert

Last time, I talked about the bare basic steps to get up and running with browser automation.   Today I'm going to show some test organization to use in your test suites.

It's important to remember what our goals are as developers.   We're not testing every limit of the system.   Our browser tests are basically acceptance tests.   Let QA do the exploratory testing.   We want clearly defined paths that are repeatable and stable to ensure no changes to code break existing functionality.   How many times have you changed a UI policy or UI policy action only to have something unexpected happen in another state on the form?   With complex processes, this is a very real possibility.


I write tests that will do the following:

  1. Take the quickest path through a process.   (ie: Create a change request, set it for approval, approve it, work it, close it.)
  2. Take a convoluted path through a process. (ie: Create a change request, set it for approval, move it back to planning, move to approval, move it back to planning, etc.)
  3. Reproduce any defects that have been found. (ie: Create a change request, set it for approval, reject it, make sure that the record state field didn't change but the approval value did change.)

For me, right now, that's over 60 tests for change management alone (given different change types, different ci types, different number of times going through a convoluted path).


The first thing to do is to write code to perform page operations.   This code needs to live outside the test class, so on either the page object class, or some extension of the page object class that will be used in the test.

Example:

public void createChangeRequestByType(String changeType) {

    driver.get(getBASE_URL() + "change_request.do");

    useSelectElement(type, changeType);

    shortDescription.sendKeys("Testing a " + changeType + " change request");

    insertAndStayButton.click();

    sys_id = sysId.getAttribute("value");

}

This is repeatable with different change types.   Of course, what if different types require different fields before proceeding?

public void createChangeRequestByType(String changeType) {

    driver.get(getBASE_URL() + "change_request.do");

    useSelectElement(type, changeType);

    shortDescription.sendKeys("Testing a " + changeType + " change request");

    fillOutNewFormFieldsByType(changeType);

    insertAndStayButton.click();

    sys_id = sysId.getAttribute("value");

}

public void fillOutNewFormFieldsByType(String changeType) {

    switch(changeType) {

            case "Standard" :

                    fillOutStandardNewFields();

                    break;

            case "Emergency" :

                    fillOutEmergencyNewFields();

                    break;

            case "Routine" :

                    fillOutRoutineNewFields();

                    break;

            default:

                    fillOutStandardNewFields();

                    break;                    

    }

}

And, of course, add in the functions for each case in the switch statement.

Eventually, you'll have a lot of functions that all handle some portion of the workflow of the form.   Then your tests in your test classes become concise and very easy to read/debug:

@Test

public void testStandardChangeRequestHappyPath() {

    change.createChangeRequestByType("Standard");

    change.moveChangeRequestToRequested();

    change.approveChangeRequest();

    change.workChangeRequest();

    change.closeChangeRequest();

}

@Test

public void testStandardChangeRequestRejectionPath() {

    change.createChangeRequestByType("Standard");

    change.moveChangeRequestToRequested();

    change.rejectChangeRequest();

    change.closeChangeRequest();

}


@Test

public void testRoutineChangeRequestHappyPath() {

    change.createChangeRequestByType("Routine");

    change.workChangeRequest();

    change.closeChangeRequest();

}

Even a non-programmer could look at those blocks of code and have a reasonable idea as to what was going on in the tests at a high level.

Next, let's talk assertions.


On the BaseTest class I have written a few functions that take a WebElement as an argument and determine if that element is read-only or mandatory.   Using those functions, we can ensure our UI Policies are working in the correct states.


public void standardNewRecordAssertions() {

    assertTrue("Short Description is mandatory on new standard changes", change.isMandatory(change.shortDescriptionSpan));

    assertTrue("Opened By is read only", change.isReadOnly(change.openedBySpan));

}


You may be wondering why the syntax includes the page object, where above they functions do not.   I buy in to Martin Fowler's view of Page Objects: even though the assertions being repeatable is nice, the page object isn't supposed to do the asserting, the test is.   (Of course, I'm also not truly encapsulating my page operations in private variables with getters and setters, but that's more of a convenience since there are so many variables and the classes are long enough as they are.   In all honestly I do need to do that at some point.)


Inserting that method appropriately into a function can tell us if the user is getting the correct field states, given a form state.   Of course, this is where most of your test failures can come from, I've found.   Sometimes the UI Policies take a while to load, or at least, take long enough to where the test will fail the assertion.   Tread carefully on these.   It may be necessary to use the ExpectedConditions class and WebDriverWait.


WebDriverWait wait = new WebDriverWait(driver, 30);

wait.until(ExpectedConditions.visibilityOf(change.shortDescription));


WebDriverWait allows you to force the page to wait for a specific condition.   These conditions are defined with the ExpectedConditions class.   You can also craft your own ExpectedConditions and pass those into the until function.


wait.until(new ExpectedCondition<Boolean>() {

                                      public Boolean apply(WebDriver d) {

                                                return d.findElement(By.id("my-element")).isDisplayed();

                                      }

                            });


I haven't needed to make my own custom conditions yet, so I don't have a great deal of experience with them.


I'm currently working on testing that includes the left nav pane of the form.   Working with iFrames in Selenium is a pain, and most of my testing really is concerned with the form itself, not the nav pane.   However, I do want to get some tests that ensure that clicking on links yields the correct results.   Spoiler alert: the page object that has the left nav links is over 500 variables (at least for my instance it is)!


That's all for this time.   Happy testing.