Introduction

The purpose of Code First API Library, Scaffolding & Guidance for Coded UI Tests (here after just "CodedUI CodeFirst") is to help you write more maintainable Coded UI tests for your web apps.

When building Web UI Tests the way that's proposed here - you aren't going to be recording test scripts nor building UI Maps. Instead, you'll be writing Page Objects that are interacting with the elements on the page.

CodedUI CodeFirst contains three major parts which can be used individually or together. When you're beginning a new project you'll probably use all these parts while in an existing project you might just pick a part that's applicable for your current situation.

The three parts are

Get started

1. Install the NuGet package

Install-Package CodedUI.CodeFirst

2. Set the base Uri

Create a TestConfiguration class that implements and exports the IWebUITestConfiguration interface. Return the base Uri of your web app under test in the GetBaseUri method.

[Export(typeof(IWebUITestConfiguration))]
public class TestConfiguration : IWebUITestConfiguration {
        
   public Uri GetBaseUri(string name) {
      return new Uri("http://localhost:8080");
   }
}

3. Create the Page Object

Create a Page Object that'll interact with the web page under test. Simply inherit the Page class and then write methods to interact with the page and to expose its state. The Browser property gives you access to the browser window and the extension methods give you an improved API for finding the controls.

public class Home : Page {
   public bool MainMenuContainsItem(string text) {
      return Browser
         .Find(new { Id = "menu" }) 
         .GetChildren()
         .Any(x => x.GetInnerText() == text);
   }
}

4. Write the test

Create a Test Class and derive it from the PageTest<T> base class. The base class will manage the browser instance and Page Object instance for you.

Write a test method that interacts with your page object to drive the UI and to verify the expected behavior.

[CodedUITest]
public class HomeTests : PageTest<Home> {

   [TestMethod]
   public void MainMenuContainsHome() {
      Assert.IsTrue(TestedPage.MainMenuContainsItem("Home"));
   }
}

Samples

Check out the CodedUI CodeFirst sample app in the source code.

Extension Methods

These extensions methods adds functionality to UITestControl and HtmlControl to simplify finding of , and interaction with, the controls on the web page.

Below are some samples of how CodedUI CodeFirst can help you find and interact with elements on the web page. Make sure to check out the API docs for a complete reference.

Find

Find a control by its Id

Finds the HtmlControl with the specified Id. This method will search for controls which id contains the specified Id - so it plays nicely with ASP.NET generated Id's.

//any element type
HtmlControl control = browser.Find("controlId");


//div with id 'controlId'
HtmlDiv div = browser.Find<HtmlDiv>("controlId");

Find a control by specifing find criteria

Finds the HtmlControl matching the specified criteria. The criterias object is of an anonymous type which properties are one or more of the property names defined in the PropertyNames member of the HtmlControl you're searching for.

//find first link
HtmlControl control = browser.Find(new { TagName = "A" });
 
//find link with inner text 'Log On'
HtmlHyperlink link = browser.Find<HtmlHyperlink>(new { InnerText = "Log On" });

Narrowing and filters

You can chain calls to Find in order to limit the search scope for the following Find calls.
Find the 2nd row in the first table.

HtmlRow = Browser
   .Find<HtmlTable>()
   .Find<HtmlRow>()
   .Instance(2);


In order to find an instance of a control within your find results you can use the Filter and Instance methods.
Find the 2nd hyperlink in the table cell at the 3rd row and the 1st column.

HtmlHyperLink link = Browser
   .Find<HtmlCell>()
   .Filter(new {
      RowIndex    = "2",
      ColumnIndex = "0"
   })
   .Find<HtmlHyperlink>()
   .Instance(2);

Interact

Click on things

With clicks and checks you can either call the methods on the HtmlControl itself or query for the control to click or check by passing a find criterias object as parameter.

Browser
   .Find<HtmlButton>("save")
   .Click();

Browser.Click(new {Id = "save"});

Filling in forms

You can set the value of a single input field by calling SetEditText.

//set the value of the html input with Id 'userName' to 'Chris'
Browser.SetEditText("userName","Chris");


When filling in larger forms - SetEditTexts might be convenient as it allow you to pass in a fieldModel. SetEditTexts will map the names of the property names in the field model to input fields with equivalent Id's.

Browser.SetEditTexts(new { 
   UserName        = userName,
   Email           = email,
   Password        = password,
   ConfirmPassword = password
});

Page Object Pattern

The Page Objects are simply the model of the UI of your web app. Writing these page objects will essentially give you a DSL of your web app. Page Objects interacts with their corresponding web page and there's usually one Page Object for each page.

Interacting with a Page Object could look something like this.

Page.Launch<Products>()
   .NavigateToCreateProduct()
   .CreateNewProduct(productName: "My Product", price: Amount());


Page objects should only interact with the underlying page and expose its state. Page objects should not do assertions, instead craft the methods in such a way so it supports assertions in the calling test method.

Give the methods of the page objects clear names written in the language of the business. This will make the tests readable like a manual test case and will by then increase their maintainability and drive the definition of the app DSL.

All page objects should derive from the Page base class. It will simplify handling of the browser instance as well as launching pages and navigating to other pages.

The PageTest base class can be derived by test classes to automate the launching and closing of the specified Page Object type.

Below are some samples of how CodedUI CodeFirst can help you implement Page Objects. Make sure to check out the API docs for a complete reference.

Page

All Page Objects should derive from Page. It'll give you easy access to the browser instance as well as helper methods. Attributes like ClearCookiesAttribute and EntryUriAttribute tells the Page Launcher how the page should be launched. In this example all browser cookies are cleared and the browser is navigated to baseUri/Account/LogOn.

[ClearCookies]
[EntryUri("/Account/LogOn")]
public class LogOn : Page {

   public LogOn LogOnUser(string userName, string password) {
      
      Browser.SetEditTexts(
         new {
            UserName = userName,
            Password = password
         });

      return this;
   }

   public Registration NavigateToRegistration() {
      Browser.Click(new {InnerText = "Register"});
      return NavigatedTo<Registration>();
   }
}


Launching and using the above Page Object to log on an user would look like this:

Page
   .Launch<LogOn>()
   .LogOnUser(userName: "chris", password: "secret");


PageTest

The PageTest base class will handle the launching and closing of the specified Page Object. Derive your test classes from this base class. The Page Object instance is accessible through the TestedPage property. Notice the usage of the form helper methods below to generate string values.

[CodedUITest]
public class AccountRegistrationTests : PageTest<Home> {

   [TestMethod]
   public void UserIsLoggedInAfterSuccessfullAccountCreation() {
            
      string userName = Name();

      var home = TestedPage
         .NavigateToLogOn()
         .NavigateToRegistration()
         .CreateNewAccount(userName, Email(), Password());
            
      Assert.IsTrue(home.IsLoggedInUserDisplayed(userName));
   }
}


Configuration

The Page Objects holds a reference to the current test configuration. Currently the only thing the configuration exposes is the base Uri that'll be used for launching pages. A type implementing and exporting the IWebUITestConfiguration interface must be present in the same project as the Page Object.

The GetBaseUri method must return an Uri. You can name different Uri's and specify which base uri you want to use for a specific Page Object by decorating it with the BaseUriAttribute.

[BaseUriAttribute("Intra")]
public class IntraHome : Page {
   //impl omitted
}


[Export(typeof(IWebUITestConfiguration))]
public class TestConfiguration : IWebUITestConfiguration {
        
   public Uri GetBaseUri(string name) {
      if(name == "Intra") {
         return new Uri("http://intra.contoso.local");
      }
      //default
      return new Uri("http://www.contoso.local");
   }
}


Test Case Scaffolding

Test Case Scaffolding reads the steps and parameters from a TFS Test Case work item (MSF for Agile v5) and creates a test class based on that.
Just type "Scaffold TestCase {test-case-id}" in the Package Manager Console. The scaffolder will create a test class with methods with names based on the test case. The test method will contain the test steps as comments so you just have to implement page objects and make assertions according to the steps.

PM> Scaffold TestCase 123


The Test Case Scaffolding is implemented as a T4Scaffolding custom scaffolder.

There are two types of templates; one for plain Test Cases and one for Test Cases with parameters (iterations).

The test class that gets scaffolded doesn't inherit from PageTest - so that's probably one thing that you will need to tweak once the test class is created.

Plain Test Case

The class name is named by the title of the test case followed by its Id. The test method is attributed with metadata such as work item id and description. The body of the test method contains all the steps from the test case as comments.

[CodedUITest]
public class PlainTestCase123 {
   [TestMethod]
   [WorkItem(123)]
   [Description("Plain test case")]
   [Owner("user")]
   public void PlainTestCase() {
	
      //ACTION: perform this
      //ASSERT: verify that
   }
}

Parameterized Test Case

A parameterized test case will get one test method for each iteration scaffolded. The parameters and their associated values are accessible through the parameters dynamic method parameter.

[CodedUITest]
public class ParameterizedTestCase456 {
	
   protected IList<object> Iterations;
	
   [TestInitialize]
   public void Initialize(){
      Iterations = new [] {
         new {
	    parameterOne = "valueOne",
	    parameterTwo = "valueTwo"
         },
	 new {
	   parameterOne = "valueThree",
	   parameterTwo = "valueFour"
	 }
      }
   }
	
   [TestMethod]
   [WorkItem(456)]
   [Description("Parameterized test case")]
   [Owner("user")]
   public void ParameterizedTestCaseIteration1() {
      ParameterizedTestCase(Iterations[0]);
   }
	
   [TestMethod]
   [WorkItem(456)]
   [Description("Parameterized test case")]
   [Owner("user")]
   public void ParameterizedTestCaseIteration2() {
      ParameterizedTestCase(Iterations[1]);
   }
	
   private void ParameterizedTestCase(dynamic parameters){
		
      //ACTION: perform this
      //ASSERT: verify that
		
      //parameters.parameterOne, parameters.parameterTwo
   }	
}

Last edited Mar 9, 2012 at 8:32 AM by chrislof, version 8