The absolute basics 

  1. Open Visual Studio 
  2. File -> New -> Project -> Select class library 
  3. Give it a name, the location and add a solution name too. You can click OK 
  4. It will automatically generate a template for you. 
  5. Click on Class1.cs in the Solution Explorer on the right side. Hit Delete to remove it, we won’t need that file. 

Nuget packages 

nuget-logo

  1. Right click on the Solution (you can find it in the Solution Explorer window which is on the right side)
  2. Click on Manage NuGet packages for solution… 
  3. Select browse tab, Search for the below items, click on them, on the right side of the window, select your project and click on Install. A popup will appear, click on OK:  
    • NUnit (By Charlie Poole) 
    • NUnit3TestAdapter (By Charlie Poole)
    • NUnit.Console (By Charlie Poole. ConsoleRunner, NUnitProjectLoader, NUnitV2Driver, NUnitV2ResultWriter, TeamCityEventListener, VSProjectLoader will also downloaded) 
    • LightBDD.NUnit3 (By Wojciech Kotlarski. LightBDD.Core, LightBDD.Framework, LightBDD.NUnit3 will also be installed) 
    • Selenium.WebDriver (By Selenium Committers) 
    • Selenium.Support (By Selenium Committers) 
    • Selenium.WebDriver.ChromeDriver (By jsakamoto) 
    • Selenium.Firefox.WebDriver (By jbaranda) 
    • Selenium.WebDriver.EdgeDriver (By pepen_microsoft) 
    • Selenium.WebDriver.IEDriver (By jsakamoto) 
    • log4net (By the Apache Software Foundation) 
  4. These packages are now downloaded and added to the project’s dependencies. 
  5. You can see the dependent packages by opening the packages.config file (you can find it in the Solution explorer) 

Visual Studio Extensions 

vstudio-logo

  1. Click on Tools in the Visual Studio menu and select Extensions and Updates… 
  2. In the popup window select Online tab from the left side 
  3. You can search for extensions with the input field located in the top right corner 
  4. We’ll need NUnit 3 Test Adapter extension in order to run our tests from Visual Studio 
  5. Select that extension from the list, click on download and follow the installation steps 
  6. We’ll also use the LightBDD for Visual Studio extension to help with generating our LightBDD files 
  7. So please install that also 

The AssemblyInfo file 

  1. The AssemblyInfo.cs contains all the information regarding your assembly file 
  2. You can rename your assembly file here, add Copyright information, company info, and also set additional properties 
  3. For us, the most important is to add the ConfiguredLightBDDScope assembly attribute and to set the parallelization 
  4. We can do the first by adding this additional line to the code: [assembly: ConfiguredLightBddScope] 
  5. It will have a red underline, because we didn’t declare that class yet, but don’t mind that for now, we will take care of it soon. 
  6. Parallelization can be set by adding the following properties: 
    • [assembly: Parallelizable( ParallelScope.Fixtures )]  
    • [assembly: LevelOfParallelism( 5 )] 
  7. These will also have red underlines. We can fix the parallelization attribute issues by clicking on the text with the red underline and hitting Ctrl+. (or right click on it and select Quick actions and Refactoring…). And from the dropdown, select “using NUnit.Framework” 
  8. An additional line will be placed next to the using statements on the top, and the red underlines will be disappear, and the attributes will change color. 
  9. Next we will set up the basic structure of our project and after that we’ll take care of the ConfiguredLightBddScope attribute that is still underlined. 

Basic structure of the project 

  1. We need to add 3 folders to our root folder of our project. 
  2. We can do that by right clicking on the project (which has a C# in a green square icon) –> Add –> New folder 
  3. We’ll add: PageObjects, Resources and Tests folders 
  4. Add another folder named Utils to the Resources folder (by right clicking on the Resources folder, and do as you did with the project) 
  5. We now have our basic folder structure. 
  6. The PageObjects folder will contain all the classes that will deal with the test logic and test variables. 
  7. The Tests folder will contain our LightBDD files that has the structure of our tests, the steps needs to be done in a scenario, and which method run in those steps.
  8. The Resources folder will contain any additional resource. It can contain some helper classes, 3rd party tools, text files, pictures, etc 
  9. The Utils folder will contain our helper classes 

ConfiguredLightBddScope 

lightbdd-logo

  1. As I promised, we’ll take care of that red underline (which means an error) in our AssemblyInfo file. 
  2. Open the Resources folder (by clicking on the triangle icon before the folder in the Solution Explorer window) 
  3. Right click on the Utils folder and select Add –> Class… 
  4. A popup will appear. You don’t need to be deal with anything, just write a Class name to the input field of the bottom of this window. Our class name will be: ConfiguredLightBddScopeAttribute 
  5. A simple class will be generated for you. We need to fill in the rest. 
  6. Derive this class from LightBddScopeAttribute. You can do this by writing colon and the superclass/parent class name after your class ( classConfiguredLightBddScopeAttribute : LightBddScopeAttribute ). 
  7. Make this class public (by writing public before class ConfiguredLightBddScopeAttribute … ), and import LightBDD.NUnit3 (right click on LightBddScopeAttribute, Quick refactor, select using LightBDD.NUnit3) 
  8. We will use this class to generate the report after every testrun. 
  9. You need to paste the following code to the class body: 

Code in ConfiguredLightBddScopeAttribute class  Expand source:  

protected override void OnConfigure(LightBddConfiguration configuration) 
   { 
   string lProjectRootDir = AppDomain.CurrentDomain.BaseDirectory; 

 if (lProjectRootDir.Contains("bin")) 
   { 
   string lStringToReplace = lProjectRootDir.Substring(lProjectRootDir.IndexOf("bin", StringComparison.Ordinal) - 1); 
   lProjectRootDir = lProjectRootDir.Replace(lStringToReplace, ""); 
   } 
   configuration 
   .ReportWritersConfiguration() 
   .AddFileWriter<PlainTextReportFormatter>( Path.Combine( lProjectRootDir, "Reports", "YourProject_{TestDateTime:yyyy-MM-dd-HH_mm_ss}.txt" ) ); 
   configuration 
   .ReportWritersConfiguration() 
   .AddFileWriter<HtmlReportFormatter>( Path.Combine( lProjectRootDir, "Reports", "YourProject_{TestDateTime:yyyy-MM-dd-HH_mm_ss}.html" ) ); 
   configuration 
   .ReportWritersConfiguration() 
   .AddFileWriter<XmlReportFormatter>( Path.Combine( lProjectRootDir, "Reports", "YourProject_{TestDateTime:yyyy-MM-dd-HH_mm_ss}.xml" ) ); 
  } 

You can eliminate the red underlines by the same method we used previously. Right click on them, select the first option from the drop down and select the first (using xxx) option from the popup. 

Change YourProject texts in the AddFileWriter lines to your project’s name (do not use whitespace, dash, dot, or special characters). 

So our code will look something like this: 

Whole ConfiguredLightBddScope class  Expand source  

using System; 
  using System.Collections.Generic; 
  using System.IO; 
  using System.Linq; 
  using System.Text; 
  using System.Threading.Tasks; 
  using LightBDD.Core.Configuration; 
  using LightBDD.Framework.Reporting.Configuration; 
  using LightBDD.Framework.Reporting.Formatters; 
  using LightBDD.NUnit3; 

namespace YourProject.Resources.Utils 
 { 
  public class ConfiguredLightBddScopeAttribute : LightBddScopeAttribute 
  { 
  protected override void OnConfigure(LightBddConfiguration configuration) 
  { 
  string lProjectRootDir = AppDomain.CurrentDomain.BaseDirectory; 

 if (lProjectRootDir.Contains("bin")) 
  { 
  string lStringToReplace = lProjectRootDir.Substring(lProjectRootDir.IndexOf("bin", StringComparison.Ordinal) - 1); 
  lProjectRootDir = lProjectRootDir.Replace(lStringToReplace, ""); 
  } 
  configuration 
  .ReportWritersConfiguration() 
  .AddFileWriter<PlainTextReportFormatter>( Path.Combine( lProjectRootDir, "Reports", "YourProject_{TestDateTime:yyyy-MM-dd-HH_mm_ss}.txt" ) ); 
  configuration 
  .ReportWritersConfiguration() 
  .AddFileWriter<HtmlReportFormatter>( Path.Combine( lProjectRootDir, "Reports", "YourProject_{TestDateTime:yyyy-MM-dd-HH_mm_ss}.html" ) ); 
  configuration 
  .ReportWritersConfiguration() 
  .AddFileWriter<XmlReportFormatter>( Path.Combine( lProjectRootDir, "Reports", "YourProject_{TestDateTime:yyyy-MM-dd-HH_mm_ss}.xml" ) ); 

 } 
 } 
 } 

 

Go back to the AssemblyInfo.cs file, right click on the ConfiguredLightBddScope text, and import the namespace of your ConfiguredLightBddScopeAttribute class (it will be something like using YourProjectName.Resources.Utils). Red underline should be removed, and color of the text should change. 

 

Our PageObjects and Creating CoreObjects.cs 

  1. Next we will create our base PageObject class, and another one for get some examples there too. 
  2. Currently we are using a CoreObjects.cs class file as our base for our PageObject classes. It contains the general methods and variables for our other PageObjects classes.
  3. Add a new class to the PageObjects folder, like we did it before with the Utils folder. Name it CoreObjects 
  4. Mark it as public abstract class (public abstract class CoreObjects). This means that we won’t use this class directly just use it as the parent of the rest of the PageObjects classes (If you checked some previous source code, we didn’t use it as abstract, but for the future, it’s better practice, so we’ll use like that). 
  5. Let’s create some variables and methods for our CoreObjects! 
  6. In the class body add the following variables/fields/property:
    • private string mOurBaseText = “Some example text for showing things”; 
    • protected IWebDriver mDriver; 
    • public string TestName { get; set; } 
  7. Created a constructor: protected CoreObjects( IWebDriver aDriver ) { this.mDriver = aDriver; } 
  8. Abstract classes cannot be initialized, so we only need our constructor, to pass the same driver to the constructors of the derived classes. 
  9. Import OpenQA.Selenium using as we did before, to be able to use IWebDriver 
  10. Now let’s add some methods! 
  11. All methods should be either protected or public (we won’t use this class directly, so no reason to use private methods) 
  12. Add SendSampleTextToInputField method: 

SendSampleTextToInputField method  Expand source :

protected void SendSampleTextToInputField( By aBy )
  {
  IWebElement lElement = mDriver.FindElement( aBy );
  lElement.SendKeys( mSomeRandomSampleText );
  }
  1. AddTryClickElementIfVisiblemethod too: 

TryClickElementIfVisible method  Expand source  

using OpenQA.Selenium;
  namespace YourProject.PageObjects
  {
  public class SamplePageObject : CoreObjects
  {
  private readonly By mMyFavoriteButtonOnPage = By.XPath( "//a[ @id = 'my_fav_btn' ] " );
  private readonly By mAwesomeInputField = By.XPath( "//input[ @id = 'awesome' ] " );
  public SamplePageObject(IWebDriver aDriver)
  : base ( aDriver )
  { }
  public override void SetTestName( )
  {
  TestName = "SampleTest";
  }
  public void PlsClickMyFavBtnAndSendTextToInput()
  {
  TryClickElementIfVisible( mMyFavoriteButtonOnPage );
  SendSampleTextToInputField( mAwesomeInputField );
  }
  }
 }
  1. You can even add abstract methods, like: protected abstract void SetTestName( );

 In the end our CoreObject class looks something like this: 

CoreObjects class  Expand source  

using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;
  using OpenQA.Selenium;
  namespace YourProject.PageObjects
  {
  public abstract class CoreObjects
  {
  private readonly string mSomeRandomSampleText = "SampleText";
  protected IWebDriver mDriver;
  public string TestName { get; set; }
  protected CoreObjects( IWebDriver aDriver )
  {
  this.mDriver = aDriver;
  }
  protected void SendSampleTextToInputField( By aBy )
  {
  IWebElement lElement = mDriver.FindElement( aBy );
  lElement.SendKeys( mSomeRandomSampleText );
  }
  protected bool TryClickElementIfVisible( By aBy )
  {
  try
  {
  IWebElement lElement = mDriver.FindElement( aBy );
  lElement.Click( );
  return true;
  }
  catch( NoSuchElementException lNoSuchElemEx )
  {
  Debug.WriteLine( "Could not find element with the given locator. Exception thrown: " + lNoSuchElemEx );
  return false;
  }
  catch( ElementNotVisibleException lElementNotVisibleEx )
  {
  Debug.WriteLine( "Element not visible with the given locator. Exception thrown: " + lElementNotVisibleEx );
  return false;
  }
  }
  public abstract void SetTestName( );
  }
  }  

 

Create another sample PageObjects class 

  1. Now let’s create another PageObjects class. 
  2. Right click on the folder and add another class, let’s name it for now: SamplePageObject 
  3. Let’s derive it from CoreObjects and make it public ( public class SamplePageObject : CoreObjects ) 
  4. We got a red underline, because we have an abstract method which is not implemented in the class 
  5. If you right click on the classname, select Quick refactor option, and implement abstract method option from the popup, it’ll generate some really basic implementation for you. 
  6. You can leave most of it, but let’s implement the method body. Remove the throw line from there, and insert: TestName = “SampleTest”; 
  7. This will set the TestName property which we declared in the CoreObjects class to the SampleTest string. 
  8. We can declare more variables and methods here, and we can also used the methods/variables which we delcared in the CoreObjects class. 
  9. Let’s create a dummy member locator variable (member means that on the class level): private readonly By mMyFavoriteButtonOnPage = By.XPath( “//a[ @id = ‘my_fav_btn’ ] ” ); 
  10. We need to import the Selenium related using also here to be able to use the By. 
  11. Create a constructor for this class also and pass the parent’s parameter for it ( public SamplePageObject(IWebDriver aDriver) : base ( aDriver ) { } ) 
  12. Now let’s create a function where we’ll use the methods from the parent class: 

PlsClickMyFavBtnAndSendTextToInput  Expand source  

public void PlsClickMyFavBtnAndSendTextToInput()
  {
  TryClickElementIfVisible( mMyFavoriteButtonOnPage );
  SendSampleTextToInputField( mAwesomeInputField );
  }  

 

Now if we initialize SamplePageObject class elsewhere, we can use our functions. 

The whole class looks something like this: 

SamplePageObject class  Expand source  

using OpenQA.Selenium;
  namespace YourProject.PageObjects
  {
  public class SamplePageObject : CoreObjects
  {
  private readonly By mMyFavoriteButtonOnPage = By.XPath( "//a[ @id = 'my_fav_btn' ] " );
  private readonly By mAwesomeInputField = By.XPath( "//input[ @id = 'awesome' ] " );
  public SamplePageObject(IWebDriver aDriver)
  : base ( aDriver )
  { }
  public override void SetTestName( )
  {
  TestName = "SampleTest";
  }
  public void PlsClickMyFavBtnAndSendTextToInput()
  {
  TryClickElementIfVisible( mMyFavoriteButtonOnPage );
  SendSampleTextToInputField( mAwesomeInputField );
  }
  }
  }  

Obviously this class is not very useful, just a sample of how we can build our pageobjects by inheritance. 

Setting up our tests 

  1.  We can generate skeletons for LightBDD based tests by right click on the Tests folder –> Add –> New Item… 
  2. Search for LightBDD.NUnit3: Feature Test Class item type 
  3. Select it, and give it a name. For example: YourProjectMainPage. Hit OK 
  4. It’ll generate two class file for you in the Tests folder (one will be named the name you gave it, and you can see the other, if you open the first class file by clicking on the triangle icon before it, in the Solution Explorer. It will named like YourProjectMainPage.Steps.cs) 
  5. The main file will be the blueprint for your tests, and the Steps file will be the implementation file. 
  6. Open the main one. Re-write the Label( “FEAT-1” ) to something like Label( “MAIN_PAGE_TESTS” ) 
  7. Write a description for it in the FeatureDescription attribute (something like: [FeatureDescription( ”Test the main functionalities of the main page” )] ) 
  8. Remove the first method and attributes, because it’s different from the second one, and we’ll only use the second type ( the one which has _ => before the function calls ) 
  9. Copy-paste the second method + attributes again, because in this example, we’ll have two test scenarios. 
  10. Re-write both label attributes to represent what is that scenario about. 
  11. Rename both methods to represent those test scenarios ( Do not use whitespaces or special characters. Only use underscore and alphanumeric characters. For example: Check_My_favorite_button_functionality ) 
  12. Create steps for the scenarios. Don’t be afraid, every step will have red underline, because we didn’t implement them yet. 
  13. Steps should begin with a Gherkin keyword (Given, And, When, Then), and it should have underscores in places of whitespaces ( for example: _ => When_I_click_my_favorite_button( ) ) 
  14. You can add parameters to the step methods too, which will help re-usability ( for example: _ => When_I_go_to_the_site( ”www.mysite.com” ) ) 
  15. These parameters can be anything that can be passed as parameters in a C# method. 
  16. You can even declare variables and pass those as parameters ( Then_I_should_see_the_correct_url( mMainPageUrl ) ) 
  17. So if you finished with the blueprint class, it should look something like this: 

Blueprint class for your tests  Expand source  

using LightBDD.Framework;
  using LightBDD.Framework.Scenarios.Basic;
  using LightBDD.Framework.Scenarios.Extended;
  using LightBDD.NUnit3;

namespace YourProject.Tests
  {
  [Label( "MAIN_PAGE_TESTS" )]
  [FeatureDescription( "Test the main functionalities of the main page" )]
  public partial class YourProjectMainPage
  {
  private string mMainPageUrl = "www.mysite.com";

[Label( "GO_TO_SITE" )]
  [Scenario]
  public void Go_to_site_and_check_url( )
  {
  Runner.RunScenario(
  _ => When_I_go_to_the_site( mMainPageUrl ),
  _ => Then_I_should_see_the_correct_url( mMainPageUrl )
  );
  }

[Label( "MY_FAVORITE_BUTTON" )]
  [Scenario]
  public void Check_My_favorite_button_functionality( )
  {
  Runner.RunScenario(
  _ => Given_I_am_on_the_main_page( ),
  _ => When_I_click_my_favorite_button_and_send_text_to_input_field( ),
  _ => And_I_set_the_test_name( ),
  _ => Then_I_should_see_something_cool( )
  );
  }
  }
  }  

 

Creating Hooks.cs 

  1. We can make our life easier if we put our setup/teardown methods to a different file. 
  2. For this, we will create Hooks.cs (which is a simple class) in the Tests folder. 
  3. Make Hooks class public and derive it from FeatureFixture 
  4. Add 2 fields (member variables) to it:  
    • protected IWebDriver mDriver; 
    • protected SamplePageObject mSamplePageObject;  
  5. Add two methods: SetupTests() and TearDown() which will fire up the driver and initialize the PageObjects, and quit the driver at the end of every scenario: 

Hooks Methods  Expand source  

[SetUp]
  public void SetupTests( )
  {
  mDriver = new ChromeDriver( );
  mDriver.Manage( ).Window.Maximize( );

mSamplePageObject = new SamplePageObject( mDriver );
  }

[TearDown]
  public void TearDown()
  {
  mDriver.Quit();
  mDriver = null;
  }  

Implementing step definitions 

  1. Next, we’ll need to implement the definitions for those steps. 
  2. Open your implementation class ( for example: the YourProjectMainPage.Steps.cs file ) 
  3. Change the parent class from FeatureFixture to Hooks 
  4. Write implementations based on your created steps in the blueprint file for your steps. You can set all methods to private void. 
  5. It should look like something like this: 

Step definition implementations  Expand source  

using System;

namespace YourProject.Tests
  {
  public partial class YourProjectMainPage : Hooks
  {
  private void When_I_go_to_the_site( string aUrl )
  {
  mDriver.Url = aUrl;
  }

private void Then_I_should_see_the_correct_url( string aUrl )
  {
  mSamplePageObject.CheckIfIAmOnPage( aUrl );
  }

private void Given_I_am_on_the_main_page( )
  {
  mSamplePageObject.CheckIfIAmOnPage( "www.mysite.com" );
  }

private void When_I_click_my_favorite_button_and_send_text_to_input_field( )
  {
  mSamplePageObject.PlsClickMyFavBtnAndSendTextToInput( );
  }

private void And_I_set_the_test_name( )
  {
  mSamplePageObject.SetTestName( );
  }

private void Then_I_should_see_something_cool( )
  {
  Console.WriteLine( "Something cool" );
  }
  }
  }  

Finishing Thoughts 

Now it’s all set. You can create Blueprints for your feature and scenario level, implementations for your steps; PageObject classes for step logic, helper methods (don’t forget to use inheritance, class and code composition so you write reusable, robust code); utility classes that is only loosely connected logic that can help with your implementations. 

Try to use a single class for only one thing, follow the DRY (Don’t Repeat Yourself) principle to avoid code duplication, encapsulate your objects (use the smallest scope possible. If a method is working when it’s private, and no need for it to be in a bigger scope, do not use as protected or public), and comment everything that is not really self explanatory. Follow the coding convention, and write code that you wouldn’t mind review if someone gave it to you.