Professional Documents
Culture Documents
WatiN
Module Overview
Welcome back to Module 4 Testing and Automating a Website with SpecFlow and WatiN. Even though
we're using WatiN in this module to perform the browser automation. If you're using another framework
for your automation such as Selenium or Microsoft Coded UI the processes we'll go through in this
framework you're using. So in this module first off we're going to get an overview of the website that
we're going to be writing our tests for. We'll get a brief introduction to the WatiN automation framework
and we'll go ahead and write a scenario for the password strength feature of the website. We'll then go
and create our automation code for the password strength scenario and also look at how we can
refactor this to improve the code base. We'll look at how we can use WatiN hooks to dispose of
the browser instance. We'll then look how we can refactor our password strength scenario into a
scenario outline to reduce duplication in our feature file. We'll see how we can further simplify our
feature files by introducing a background and finally we'll see how we can refactor our step definitions
So before we get into writing our first scenario let's have a quick look at the demo website that we're
going to create our acceptance tests for. If I just run it we default to this Sign Up page and this is the
feature that we're going to be working with. So this Sign Up page is where a new user would go to
create a new account on this website. So we can type in a username, an email address, and we can hit
Register. And we get some simple validation errors if there's a problem so here the passwords don't
match and we have an invalid email address. So if we fix these two things (Typing) and correct
the password, when we click Register if all of the validation passes then we're taken to this
1
RegistrationComplete page and we get our username displayed to us. Also notice that we've got this
password strength, so let's go ahead now and create a feature file and our first scenario.
to click buttons and type text into text boxes and you can find out more about WatiN at watin.
org. Alternatives to WatiN also include Selenium and Microsoft's Coded UI. If you want to learn more
about Selenium or Microsoft Coded UI check out the other great courses on Pluralsight. com. We'll be
getting a brief overview of WatiN in just a moment, but if you want to go into more depth check out my
Automated Testing: End to End course. So let's have a look at some WatiN code in action. So here
we've got a NUnit test and the first thing we do is we create a new instance of this WatiN IE class. And
here we're wrapping it in a using statement so that when the instance gets disposed any unmanaged
resources will be cleaned up for us. By default when we lose this IE object the corresponding Internet
Explorer window will also be closed. And for demo purposes I've set this AutoClose property to false so
that our Internet Explorer window stays open and we can see what's happening. Also for demo
purposes I'm making a call to this BringToFrontForDemo extension method that I created just so we
ensure that IE is in the foreground and we can see what's happening when we execute this test.
So now we've got our WatiN object, two enablers to automate the Internet Explorer window, we can
start to work with it. So first off we can navigate to a specific URL by calling the GoTo method. So
here I'm opening up this Register page on my local machine. Once this navigation is complete we can
then work with elements on the page. So for example to find a text field with a given ID we say ie.
TextField and one of the ways we can locate this is to try and find it by the HTML Id. So here we're
saying give me the text field with the Id of Password. When we locate this text field we're just assigning
it to a variable here. So once we've located an element on the page we can interact with it. So for
example with this text box we can use the TypeText method to simulate a user typing text. So here
we're simulating a user typing the words Hello World into this text box. We can work with other
2
HTML elements such as divs, spans, and buttons as well. So for example if we wanted to click a button
with an Id of DoRegister we'd say ie. Button locate it by its HTML Id and once we've located it, in this
case we're not assigning it to a variable, we're just directly calling its Click method. And this will simulate
a user actually clicking on this button in the browser. So let's go ahead and run this test and see WatiN
in action. So we quickly saw then how the string Hello World was typed into the password box and how
the Register Now button was clicked. And because we've got some validation errors here we've just got
the validation messages appearing here. So that's how we can create an instance of the WatiN IE class
and use it to navigate to a specific URL and then interact with the HTML elements that exist on the
page.
Let's go ahead now and create our feature file. So for example we can think of the feature that we're
working with as the new user registration feature. So first off let's go and create a feature file and we'll
call it NewUserRegistration. So let's remove this default scenario and just tidy things up. So first off
let's add some description of the feature in this free text section and we'll use the in order to as an I
want format. So let's start with In order to get access to the member only Features As a potential
new user I want to create an account. So let's add the first scenario to this Feature. So we'll call this
Scenario Password strength indicator and in this Scenario we'll be checking that when the user
types different lengths of password that we get the correct password strength indicated to them. So let's
start with our Given, so we can say Given I'm on the registration page, When I enter a password of
Pass, Then the password strength indicator should read Poor. We could also apply a tag to our
Scenario or our Feature to indicate that it belongs to a superset of logical features. So here we can
example, to run all scenarios related to authentication, so this could also include things like logging into
3
Writing Automation for The Password Strength Scenario
So now we have our Feature file and our first Scenario, let's go ahead and create the initial version of
our automation with WatiN. So first off let's generate our step definitions and I've already set this project
first off let's go and install WatiN from NuGet. And here we'll install the WatiN package. And just Close
this and we'll add a using statement to WatiN. Core and we'll just remove this pending line here. So the
first thing we need to do is create a new IE object, but this time we can't create it with a using because
we don't want to dispose the IE instance as we need to use it in later steps. So first off let's just create
the instance and here we're using an overload of the constructor that allows us to specify the starting
URL. So here we're just going to the register page. And as in the WatiN overview I'm just going to make
Internet Explorer. And I'm just going to include this extension method in our project. So let's just build
this and we've just got a build error as I haven't included the namespace for our extension method.
So let's just grab this namespace and add it here. Now we can build and notice we get our
PasswordStrengthIndicatorTest for our scenario here. I'm just going to change the view in Test Explorer
to the project view so we can just group all of our scenarios differently from our overview of WatiN test
that we used earlier. So let's go ahead and execute this first test. And we want to see if Internet
Explorer opens correctly and it's on the right page. So we've got an error here when we tried to execute
the test. If we have a look at it we can see that it's making reference to this Interop. SHDocView and
that's because when we use WatiN we have to edit this reference and change the Embed
Interop Types property from True to False. And we can just build to make sure there's no errors and we
can try and run our test again. Notice that the test has failed again but for a different reason.
When we're using WatiN to automate Internet Explorer our tests need to execute in the single threaded
apartment threading model. So if we head to our AssemblyInfo we can change this by adding the
RequiresSTA attribute to our assembly. And this is an attribute that comes from the NUnit Framework.
4
This attribute instructs NUnit to execute tests using a single threaded apartment model. So let's close
this and build and now when we run our test we should see Internet Explorer open at the correct page.
And it does. So here we have our page Register. aspx loaded in Internet Explorer. So if we come back
to our automation steps, because we need to use this ie instance in our other steps we need
somewhere to store it. So we can use our scenario context to do this. Here we're adding our ie instance
to the ScenarioContext. Current with a key of Browser. Our next step is to enter the password of Pass
into the password text field. So first off we need to get a reference to the ie instance stored in
our ScenarioContext from the previous step. Next we want to locate the correct text box that represents
the password, so we're just using the FindById method and we're going to type the string Pass into it.
So notice here that we're just hard coding Pass as opposed to using a parameter from the step
definition. And we're going to perform some refactoring's later in the module. Unfortunately there's a bug
in WatiN that sometimes prevents events from firing on our HTML elements. So when we type a
password into the password text box every time we press a key a JavaScript event fires that updates
the password strength, but when we're using WatiN to type the text this bug means that the key press
event in JavaScript isn't fired. So as a workaround for this we can use the Eval method of the ie object
to evaluate some JavaScript in the browser. We're using jQuery here to select the password box and
force the firing of the key press event. And we can just add a comment here to explain things. So let's
go ahead and execute this test now with our second step. And we can see that our password of Pass
has been typed into the password box and our password strength is indicated to us. The final step is to
actually check that the password strength indicator reads correctly. So we expect it to read Poor. So as
before we get a reference to our ie object and the next thing we need to do is read out the string for the
password strength. So here we're locating a Div with the Id of PasswordStrength and we're just getting
its InnerHtml and assigning this to the strength variable. So now we've got the password strength
that's being displayed in the browser, we need to make a NUnit assert to check that it's correct. So here
we're expecting the string Poor and passing the actual string. Now we're at the end of this scenario,
we can dispose of our ie instance, but before we do this we just want to set AutoClose to false so we
can see what's happened in the browser for demo purposes. So let's go ahead and execute this
5
complete scenario now and see what our result is, but first just let's quickly add the NUnit
framework using statement so we can access the assert method and now run. And we can see that our
password has been typed in as before and the password strength is displayed as before, but if we head
back to Visual Studio now we can see that our test has passed. And to prove this is working let's
change the Assert and run the test again. And let's check our test result. And we can see this time
it's failed because we expected xPoor but was actually Poor. So let's just fix this up and Save and just
Whilst we have our basic automation working now we can make some improvements to the actual code
and perform some refactoring. So notice in these subsequent steps each time we have to go and grab
the ie instance from the ScenarioContext. So we have some code duplication here that we can take
care of. Also whilst we've only got one step definition creating an ie instance at the moment, if we had
to manually create an ie instance in the first step definition of every scenario we had, this would also
create some code duplication. So let's see how we can start to refactor things. If we go to our Solution
Explorer and I've added this WebBrowser class to our test project. If we have a look at this we can see
that we've got a public static class called WebBrowser and we expose a current property of type IE.
When we access this property getter we first check that the scenario context doesn't contain a browser,
if it does then we just return it, but if it doesn't contain the browser key we go ahead and create a new
one. And for demo purposes we just tell it not to close and bring it to the foreground. Once we've
created our Internet Explorer object we then just go and add it to the ScenarioContext. So now we've
got this new WebBrowser class that will deal with the creation and storing of ie instance for our
ScenarioContext, let's go and refactor our existing steps. So first off we can get rid of this line. So in the
first step here we still need to navigate to the registration page and to do this we can access our
WebBrowser. Current property, which will give us our ie instance. And then on this ie instance we can
call the GoTo method which will navigate to our register page. We can also get rid of these two lines
6
because in our WebBrowser class we're already bringing Internet Explorer to the foreground. And we
also no longer need to store our browser manually in our ScenarioContext. Again the WebBrowser
class is taking care of this for us, so let's delete these and we can see already that our step
Explorer object and we can replace this local ie variable with a reference to WebBrowser. Current. And
the rest of it stays the same as before. We still need to handle the firing of this key press event, but
again we can just replace this ie with WebBrowser. Current. Let's go to our Then step, again
remove this and we can replace our ie reference with WebBrowser. Current. We keep our Assert the
same, we can also delete our AutoClose line and replace ie Dispose with WebBrowser. Current
Dispose. So let's build this to check there's no errors. And run this test now. And we can see
our automation in progress and we can see that in Visual Studio our test passed.
So we've made some improvements to our automation code, but we still have this WebBrowser.
Current Dispose in our last step of our step definitions. We don't really want to have to worry
about disposing the WebBrowser. Current ie instance in the end of every Then step that we write.
SpecFlow provides us with a number of hooks that we can use to help us out in this instance. If we take
a look at the SpecFlow documentation at specflow. org we can see we have these attributes. Before
and after test run, before and after feature, before and after scenario, before and after scenario block,
and before and after steps. So let's take a look at how we can use the before scenario and after
scenario hooks. So I've added this ScenarioBeforeAndAfter class. If we take a look at it we can see that
I've decorated this class with the SpecFlow binding attribute and this tells SpecFlow that it should look
inside this class for bindings including these hooks. So here we've got this BeforeScenario attribute
or we could use the shortened version of Before and we're just applying this to a static void method. So
this method will be executed before each and every scenario starts and also before each and every
7
data example in a scenario outline. Similarly the AfterScenario attribute means that this After method
will get executed after each scenario finishes executing. So in this method I've added a call
to WebBrowser. Current Dispose so this will mean that after every scenario finishes executing the ie
instance will be disposed of. So we can now head back to our steps and remove this dispose line here.
So I've got the breakpoint in this After method and let's go and debug this test. So we briefly saw
Internet Explorer open then, if we pop back to it we can see our automation has taken place, Pass has
been entered, and if we hop back to Visual Studio we can see that our breakpoint has been hit in our
After method. So we can just continue this. So that's how we can use the BeforeScenario and
AfterScenario attributes to execute code before each scenario executes and also after each scenario
So now we've got our first scenario automated for password strength, we need to add more scenarios
to cover the password strengths of not only Poor but Average and Awesome. So we could go and copy
this scenario and duplicate it another two times and then change our scenario steps to use parameters
for the password and for the expected strength, but in this instance a scenario outline is a better choice.
As it will result in less duplication in our Feature file. So let's go ahead and replace this scenario with
a scenario outline. So here we've replaced the password that we type in and also our expected strength
indicator. And we've got three examples here pass where we expect Poor, Password where we expect
Average, and long password where we expect Awesome. So we need to change our existing
step definitions for our When and Then lines to take account of these parameters that we need to pass
in. So let's start with this When line. So here we're going to add a parameter and rather than typing
this fixed string literal we'll now type the password that was passed to the step definition. Similarly for
our Then step definition we'll add a parameter and we'll just rename this existing strength parameter as
ActualStrength and our expected strength will now be the strength passed in to this step definition via
the parameter that's mapped here. So if we Save this and check our Feature file notice now that both
8
password and strength have changed to italics to indicate that they're mapped to our example
columns password and strength. So let's build this and we can see now we get our three scenario
examples and let's go ahead and run all three. So that's our three scenarios executed and we're just
leaving the Internet Explorer windows open again, but if we head back to Visual Studio we can see that
our three scenario examples were executed and they all passed. So that's an example of how we can
use scenario outlines and pass the data provided in the step definitions parameters via WatiN to the
browser.
Let's now explore our second scenario. So here I've added a new Scenario for the username already in
use. So here we're saying Given I'm on the registration page, When I enter valid new user details,
But the username Mr. Awesome is already taken, When I try to proceed with registration, Then I should
see an error sorry that username is already in use. So notice here that I've chosen to use two When
lines. So I could have written this When I enter valid new user details, But the username Mr. Awesome
is already taken, And I try to proceed with registration, but I think having this second When instead of an
And makes the Scenario read a little better, but we could certainly use an And if we wanted to. Now
we've started to add additional Scenarios, we can move this tag that's currently only applicable to
this Scenario outline to the Feature level. And now all of the Scenarios within this Feature will inherit this
tag. So I've already implemented the step definitions for our username already in use scenario and
notice here that the first step definition, Given I'm on the registration page, is shared with the Scenario
outline above, but the remainder of this Scenario I've created new step definitions for. So let's check out
this When I enter valid new user details first. So this step affectively fills out our form with valid data.
And here I'm creating a random username by just grabbing the first 10 characters from a NewGuid. We
then type this random username into the UserName text field, we enter an EmailAddress and we enter
the Password and the ConfirmPassword. The next step But the username Mr. Awesome is already
taken we've got something extra going on here. So we've got a parameter for the username being
9
passed into this step definition and we're typing in this username into the UserName text field, but
notice we're doing the same thing here so there's a bit of duplication, but this means that we can reuse
this step definition anytime we need to enter valid new user details into the form. So when the
username name is already taken, because we're passing in variable data here we want to ensure that
the system has a user setup for this name. So that when we come down to check that the right
validation message is displayed we can be sure that the state of the system, ie the username exists, is
as expected before we try and perform the user registration. So we're calling this CreateUser method
on this test data class and this is a class that I've created. If we look at this CreateUser method for
demo purposes the website is hard coded to recognize the username Mr. Awesome as it being an
existing username, but obviously in a real system the users would be stored in some backend storage
system for example a database. So to ensure that this username exists we could either directly insert it
into the underlying user database table, we could go via the app service layer to create a user,
or alternatively we could do it through the UI. So the UI will be the slowest method here, the next
slowest will probably be the service layer, and the fastest will probably be the direct insert into
the database. So depending on the system you're building you'll have a number of different options that
may suite you. So once we've ensured that our username exists in the system we then go and override
the username in the web page. The next step is the When I try to proceed with registration. If we have a
look at this we can see here that we're simply clicking the button called DoRegister, but notice in the
Feature we don't actually say When I click the registration button. And this is because wherever
possible in our scenarios we want to try and be as user interface agnostic as possible. The final step
Then I should see an error. Here we're finding the list of validation errors with the Id of
ValidationErrorList and we're simply saying all of the text within this list check if it contains the message
passed through as a parameter to this step definition. And then we're simply asserting that this is true
and if it's not true we're providing a more helpful failure message. So here we're just saying that the
message was not found in validation errors. So let's execute this and see it in action. So our automation
has finished, let's go back and check that we got a passing test, which we did. And we can double-
check this is working by changing the error message here and executing it again. So now when the test
10
runs the error message that we see will be different from the error message that we expect to see. So
let's head back to Visual Studio and we can see now that our test has failed with our helpful message
that Sorry yyythat username is already in use not found in validation errors.
So now we've got a couple of Scenarios in our Feature file, we can start to think about if we can remove
any duplication from our Scenarios. If we have a look at our two Scenarios here we can see that we've
got this duplicated Given I'm on the registration page step. And if we look at the description of our
Feature it's likely that all of the Scenarios within this Feature file will start on the registration page. So
we'd have this Given I'm on the registration page step at the start of each one of our Scenarios, but
we can prevent this duplication by introducing a background to our Feature. So let's go ahead and do
that and we'll just grab the Given line here and add it as a background step. We can then remove it from
this Scenario as well and now the background for all of the Scenarios in this Feature states that Given
I'm on the registration page. If we build this there's no errors and we don't have to change any of our
step definitions even though we've moved this Given line to the background, it still maps to the same
step definition, which we can see here. So let's go ahead and execute our username already in use.
And we can see our Scenario executes the same as it did before. So when we use a background for
a Feature the background runs before each and every Scenario executes. And in the case of a
Scenario outline it will execute before each example in the Scenario outline, but the background will
execute after any Before hooks we've set up. So for example, our BeforeScenario hook here will be
executed first, followed by our background, and then the Scenario. So I've got a breakpoint here and a
breakpoint in this first Given step. If we go and debug this test we can see that the first breakpoint that's
hit is in our BeforeScenario hook, if we continue we can see now that we get our background
step executed next. And if we continue execution we'll get our Scenario executed. So that's how we can
use a background to execute one or more steps that are relevant and add context to each of the
11
Refactoring step Definitions into other Classes
As the number of Features and Scenarios in the test automation grows larger we might want to give
more thought about how our step definitions are organized. So at the minute we've got a single Feature
and a single matching step definition file. So in our Feature this Given I'm on the registration page step
we could consider this to be part of a logical set of navigation steps. So we could move its matching
step definition into a separate steps class. So let's go ahead and do this. So we'll Add a new Class and
we'll call it NavigationSteps. If we have a look at our existing steps we can now remove this Given and
paste it into our NavigationSteps file, but if we look here we also need to add the SpecFlow using and if
we look at our steps we can see that we use this Binding attribute of a public class, so we'll replicate this
in our NavigationSteps and we'll just build to check everything's okay, which it is. And if we go back to
our Feature now we can see that this Given line is still in white, which means SpecFlow is still finding a
matching step definition. It's just this time it's finding it in this NavigationSteps class. So if we run
our Scenario now we can see that it's executing as before, even though we've moved the step definition
into a separate class. If we were to remove this Binding attribute and build and check our Feature now
we can see that the Given step has changed to purple because SpecFlow can't find a matching step
definition. So let's just go and quickly add that back. So that's how we can start to organize our steps
into logical groupings inside our own classes as the number of scenarios grows over time.
The final thing we're going to address in this module is the fact that in our step code here we have the
Ids of the controls we're interacting with duplicated throughout this code. So here we're finding the
have the Ids littered throughout our steps and as we continue to add Scenarios to our Feature this
proliferation of duplicated Ids will increase. So this is a problem because if the page we're
testing changes it's Ids for whatever reason then our automation will break. For example if the register
12
button changed its Id from DoRegister to Register then when we try and execute this step the button
would not be able to be located and so the button wouldn't be clicked and we'd get an error. So we
could go and create some constants either in this steps class or perhaps in a global Ids class, but a
better way is to create a class that models the registration page. So I've already created this class and
we can use this to abstract a way the underlying Ids of the page we want to automate. So the first thing
we do here is inherit from the WatiN page class and here we're just defining a number of constants to
represent the Ids of the controls we want to interact with. Then we simply create a property, in this case
for UserName, we create a get and a set and the get property locates the TextField with the Id specified
in our UserNameId constant and just returns its Text. And in the setter we're finding it and using
the TypeText method to enter text into the field. And we're doing a similar thing for the EmailAddress,
just a Div that we're reading the content from. And similarly with the ValidationErrorListText we're
just returning the Text of the list. We also have a method called ClickRegisterButton and again this
decouples the actual button Id from our automation code. And in this example rather than creating a
constant we've just got the string literal. So the benefit of this approach is we've effectively decoupled
the Ids from our automation code and we'll see in a moment how it also makes our automation code
more readable. We can also do things like where we set the Password before in our automation step
we had to execute this Eval statement, but we can now encapsulate this in the setter for the
password property. So once we type the new password we call the Eval. So let's go ahead and look at
our steps. So I'm going to go and replace all of these steps with a version that I've modified to use this
new registration page class. So let's compare the old and the new. Before in
our When_I_enter_a_password step we had this WebBrowser. Current we're finding the Id and then
we're calling Eval to force a key press event, but now we're using these two lines. So when we create a
class that inherits from the WatiN Page object it allows us to use the Page method of the ie object to
interact with the current page that we're on in a strongly typed way. So here we're just saying we're on
the registration page and we represent this with a variable, in this case p. Once we have this page we
can use its properties and methods. So here we're just setting the Password property and when we do
13
this the setter will be called and the value we set the property to will be typed in to the password field.
And because we've encapsulated this Eval as well we no longer need to do this in our step definition.
The next step here, again we start by getting our page, but whereas before we had our actual strength
now we just set actualStrength to the read-only PasswordStrength property of the page. And let's just
creating our random UserName grabbing our page, but rather than these four lines here we replace
them with these four lines here. Again using the properties of the page. So you can see here that it's a
method rather than having to find the button and click it ourselves. And in our error message step here
rather than having to perform this kind of work ourselves we just access the ValidationErrorListText
property and check it contains our message. So by using this registration page we've also isolated
the Ids of controls and improved the readability of our step definitions. So let's build this to check there's
no errors and let's go and run all of our scenarios to check that they still work. So we can see our
password strength scenarios executing and our existing username scenario, so let's head back
to Visual Studio and we can see that our tests still pass. So that's an example of how we can refactor
our step definition code to use a strongly typed page model. There's a couple of different
conceptual styles we can use when we represent pages, if you're interested in learning more about
these styles check out my Automated Testing: End to End course and have a look at the Functional UI
Testing Module. And in this module there's also more information about the WatiN framework.
Module Summary
So that brings us to the end of this module on SpecFlow and WatiN. In this module we saw an overview
of the website under test. We took a brief tour of the WatiN automation framework. And we wrote the
first password strength scenario. We then learned how to create our automation code for this scenario
and to refactor the code to improve readability and reduce duplication. We saw how we can use
14
hooks such as the before and after hooks to dispose of the browser instance. And we then refactored
our password strength scenario to use a scenario outline. We saw how we can use backgrounds to
further reduce duplication in our scenarios. And finally how we can move step definitions into other
15