logo logo

10 Good Practices in Selenium C# Automation

10 Good Practices in Selenium C# Automation

I wanted to share with you some of the good practices I learned along the way, that I wish I knew when I first started my automation testing journey. While what we call “best practice” may not apply to all projects or all people, please read on to find advice on how you can improve your Selenium C# automation framework.

Table of Contents – Good Practices in Selenium C# Automation

  1. Coding Conventions
  2. The DRY principle
  3. Independent tests
  4. Single responsibility principle
  5. Limit the number of assertions
  6. Locator strategy
  7. Use waits
  8. Page Object Model
  9. Take screenshots
  10. Reporting

Coding conventions

These conventions exist to have a consistent-looking code and enable readers to understand the code faster by making assumptions based on previous experiences. This article focuses on C#, so the conventions found here will not apply to other programming languages.

Naming conventions

  • Use PascalCase for method and class names.
// Good
public class HomePage
// Bad
public class homepage
public class homePage
  • Use camelCase for local variables and parameters (the first word starts with lower case, following words with upper case).
  • Don’t use abbreviations – there is no limit on the number of characters a class, method, or variable name can have, so avoid creating confusion or inconsistencies by using abbreviations.
  • Avoid using the underscore character (the only exception are private variables).
// Good
private string _password = "12345";
HomePage homePage = new HomePage(driver);
// Bad
HomePage home_Page = new HomePage(driver);
  • Include the word “Test” at the end of your test classes (e.g. LoginTest, ShoppingCartTest etc.).
  • Use nouns for class names, and verbs for methods.

Layout conventions

  • Do not include more than one statement or declaration per line.
  • The comments should be added on the line preceding the code, not after the code. Also, try to make the code as clear as possible so it does not need to be explained through comments.

A good example of comment formatting:

// This methods sends the credentials.
public void Login(string name, string password)

And a bad example:

public void Login(string name, string password)
      Name.SendKeys(name); //sends the name
      Password.SendKeys(password); //sends the password

The DRY principle

DRY, short for Don’t Repeat Yourself, is a principle aimed at reducing code duplication. It’s defined as “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system”.

To achieve the DRY principle, make sure you avoid code duplication. For example, if all your tests require the user to log in, create a setup method that performs the login and runs before each test. Or declare your web elements as variables so you don’t search for them on the page each time you need to interact with them:

// Good:
private IWebElement name => driver.FindElement(By.CssSelector("#name"));
name.SendKeys("User Name");

// Bad:
driver.FindElement(By.CssSelector("#name")).SendKeys("User Name");

Independent tests

This is a very important aspect of a good and stable automation framework. If you want your tests to be reliable, avoid making one test depend on another i.e. create the test data for TestCase2 in TestCase1. This may cause unexpected test failures, which can consume valuable time spent debugging and fixing, without actually uncovering any issues in the AUT. Also, if you want to consider test parallelization (which you definitely should), you can’t control the order in which the tests run, which means that the dependent test might run before the test it depends on.

Single responsibility principle

A term introduced by Uncle Bob, author of the Clean Code book, the Single responsibility principle refers to the fact that classes should have one single responsibility, therefore only one reason to change. In test automation, this translates into short tests, that don’t validate multiple functionalities, and that should have only one reason for failure.

Consider using setup and teardown methods, to separate the preconditions (such as opening the browser, signing in) from the tests, and also to clean up after your test (close the browser, delete any test-specific data).

public void Login()
        runner.Run(new LoginTest());

public void AddItemsToCart()
        runner.Run(new AddItemsToCartTest());

public void TearDown()

The above example uses NUnit with TestProject C# SDK. The [SetUp] is the method that logs the user in, and then the test itself only checks that items are added to the cart. If there is something wrong with the login, only the setup will fail, and the test will be blocked. This way, we’ll know that the problem lies with the login functionality, and not with adding items to the cart.

Limit the number of assertions

This practice reinforces the single responsibility principle. Keep in mind that the tests should have one, or two assertions at most. If you feel the need to include more, consider splitting your large test into smaller, more granular ones, that focus on fewer validations.

Locator strategy

An important part of UI automation is using the right locators. The locator strategies available in Selenium C# are:

  • Id
  • Name
  • LinkText
  • PartialLinkText
  • TagName
  • ClassName
  • CssSelector
  • XPath

When available and unique, the most recommended locator strategy is the ID. It’s the fastest locator, and it’s also easy to use. ClassName is also a good locator strategy, but it also has to be unique, which you probably noticed is not always the case.

When ID and ClassName are not available, the following preferred locator is the CssSelector, which is a pattern that uses attributes and their values. Here’s how to use CssSelector in C# to identify a div element that follows an element with ID=password:

driver.FindElement(By.CssSelector("#password ~div"));

After CssSelector, comes the XPath, which locates the elements in the DOM structure. The LinkText and PartialLinkText are available only when the element you need to locate is a link, otherwise they cannot be used. Name and TagName should be used only if they are unique, or when you need to find multiple elements with the same name, or tag name, using the FindElements() method.

To help with identifying the best locator for your element, you can use the developer tools in your browser, or you can use a tool like TestProject to help you identify the element’s attributes:

Element Locators in TestProject

Use waits

Avoid using Thread.Sleep()

You probably heard this before, but I will say it again. Thread.Sleep() will make the test wait the amount of time passed as a parameter, statically. For example, if you set the sleep time to 5 seconds, and then try to find an element, you have to wait the whole 5 seconds before the test continues, even if it takes only 3 seconds to load the element. This adds unnecessary delays to your code. Two seconds may not seem like a lot, but if you use this in hundreds of tests, they amount to a few minutes ⏳

A good practice is to use Selenium WebDriver’s wait methods instead.

Implicit waits

The implicit wait delays the execution for the specified amount of time until the web element is found. However, unlike the Sleep, if the element is found before the time elapses, the test will continue normally. It also applies to all the web elements in the test script.

Here’s how an implicit wait looks in C#:

driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);

The downside of the implicit wait is that it only applies to the FindElement() method.

Explicit waits

Explicit waits apply only to a specific element. We use them to wait until a certain condition is met.

Explicit Wait Expected Conditions in C#
The list of available expected conditions

To use explicit waits in C#, you need the DotNetSeleniumExtras NuGet package. Here’s a code sample of an explicit wait that waits for an element to be clickable:

WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
IWebElement element = wait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.

Be mindful when mixing explicit and implicit waits

This can cause undefined behavior, unexpected wait times, and overall decrease the reliability of your tests.

Page Object Model

An already well-known good practice in UI test automation is to use a design pattern. The most popular design pattern in UI automation is probably the Page Object Model (also known as POM).  When working with POM, we separate the UI interaction methods from the actual tests. Each web page or web component is represented by its own class, where we store all the methods related to this particular page or component, which we then call from the test methods. This provides a great way to avoid code duplication and allows easier maintenance of the test framework.

No Assertions in page objects

Keep assertions out of the page object classes. If you want to include a validation inside the test class, make it return a boolean value. This way, you can use it to validate both positive and negative scenarios.

Here’s an example:

// The method inside the page object class returns true if the name is displayed, and false if it not:
public bool IsUserLoggedIn(string name)
      return DisplayName.Text.Equals(name);

// The assertions inside the test method:
Assert.IsTrue(IsUserLoggedIn("My Username");

No Selenium methods in tests

Include Selenium methods in your page object classes, not your test methods. Each web element can be declared as a private variable inside its class, and the methods should be public so they can be accessed from the test classes.

Use wrappers

Or better yet, wrap the Selenium methods inside your own methods. This reduces the dependency on any possible changes inside the Selenium libraries. It can also be useful if you often use certain methods together.

For example, to write something inside a text box, you might click the text box, clear the existing text, then enter your text. You can create a method that takes the element as a parameter and performs all these actions:

public void WriteText(IWebElement element, string text)

Take screenshots

Another good practice is to take screenshots of the failed steps. This will help you better understand where and why the step failed, and also make debugging easier. Selenium has the method GetScreenshot() available, which takes screenshots of the entire webpage or a specific element.

If you are using the TestProject SDK, you can use the TakeScreenshotConditionType enum as a parameter in the test step to decide when to take a screenshot:

TestProject Take Screenshot


Last, but definitely not least, use a good reporting tool to demonstrate the results of your test run. Not only will this help your work become more visible, but you can share the results with management, clients, or other non-technical audiences that may be interested in the various test metrics.

Test reports can also help you see how your tests progressed – how much time they take, how many passed or failed, compared to previous runs, and so on.


Even if you already know of some of these good practices in test automation, I hope this article was a good reminder of how to apply them in your work. While they might seem like a lot of work in the beginning, I promise you they will make your life easier in the long run 🤓


About the author

Andreea Draniceanu

Andreea is a QA engineer, experienced in web and desktop applications, and always looking to improve her automation skills and knowledge. Her current focus is automation testing with C#.

Join TestProject Community

Get full access to the world's first cloud-based, open source friendly testing community. Enjoy TestProject's end-to-end test automation Platform, Forum, Blog and Docs - All for FREE.

Join Us Now  

Leave a Reply

popup image

Test, Deploy & Debug in < 1 hr

Leverage a cross platform open source automation framework for web & mobile testing using any language you prefer, and benefit from built-in dashboards & reports. Free & open source.
Get Started