ZetCode

C# Selenium

last modified October 18, 2026

In this article we show how to automate web browsers and test web applications with the Selenium framework in C#.

Selenium

Selenium is a portable framework for testing web applications. Selenium runs on Windows, Linux, and macOS.

Selenium WebDriver is a collection of open source APIs which are used to automate the testing of a web application. Each browser is controlled through its own driver: ChromeDriver for Chrome, EdgeDriver for Microsoft Edge, GeckoDriver for Firefox, and SafariDriver for Safari. Selenium WebDriver supports several programming languages including C#, Java, Python, and JavaScript.

Selenium can run the browser in normal mode or in headless mode. In headless mode, the browser runs without a visible window, which is the usual setup for automated tests and CI servers.

In this article we use Microsoft Edge. The examples work with Chrome as well; we replace EdgeOptions and EdgeDriver with ChromeOptions and ChromeDriver.

Selenium Manager

In the past, the browser driver had to be downloaded manually and placed on the PATH. Since version 4.6, Selenium contains Selenium Manager, which finds the installed browser, downloads the matching driver, and caches it in the ~/.cache/selenium directory. We only need to have the browser installed.

$ dotnet add package Selenium.WebDriver

We add the Selenium.WebDriver package to the project. It also contains the WebDriverWait class used for explicit waits; the older DotNetSeleniumExtras.WaitHelpers package is no longer maintained and is not needed.

C# Selenium simple example

In the first example, we get the title of a web page.

Program.cs
using OpenQA.Selenium.Edge;

var options = new EdgeOptions();
options.AddArgument("--headless=new");

using var driver = new EdgeDriver(options);

driver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(driver.Title);

The program runs the browser in headless mode and prints the title of the page.

var options = new EdgeOptions();
options.AddArgument("--headless=new");

We pass a command line argument to the browser to run it in headless mode.

using var driver = new EdgeDriver(options);

We create an instance of the EdgeDriver. It starts the driver and a new browser session. The driver is disposed at the end of the program; the Dispose method calls Quit, which closes the browser and stops the driver process.

driver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(driver.Title);

We navigate to the given URL and get the title. The GoToUrl method waits until the page is loaded.

$ dotnet run
Example Domain

C# Selenium click button

In the following example, we click on a button and retrieve the text of an alert dialog.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test page</title>
</head>
<body>

    <button id="mybtn" onclick="setTimeout(() => alert('Hello there!'), 500)">
        Click
    </button>

</body>
</html>

The HTML file contains a button which shows an alert dialog after half a second.

Program.cs
using OpenQA.Selenium;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Support.UI;

var options = new EdgeOptions();
options.AddArgument("--headless=new");

using var driver = new EdgeDriver(options);

var url = new Uri(Path.GetFullPath("index.html")).AbsoluteUri;
driver.Navigate().GoToUrl(url);

var button = driver.FindElement(By.Id("mybtn"));
button.Click();

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
wait.IgnoreExceptionTypes(typeof(NoAlertPresentException));

IAlert alert = wait.Until(d => d.SwitchTo().Alert());
Console.WriteLine(alert.Text);

alert.Accept();

We load the HTML file from the local file system.

var url = new Uri(Path.GetFullPath("index.html")).AbsoluteUri;
driver.Navigate().GoToUrl(url);

We build a file:// URL from the path of the HTML file. The file is located in the current working directory, which is the project directory when we use dotnet run.

var button = driver.FindElement(By.Id("mybtn"));
button.Click();

We find the button by its id and press it with Click.

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
wait.IgnoreExceptionTypes(typeof(NoAlertPresentException));

IAlert alert = wait.Until(d => d.SwitchTo().Alert());

The alert does not appear immediately. We use an explicit wait: the Until method repeatedly calls the given function until it returns a non-null value or the timeout expires. Until the alert pops up, SwitchTo().Alert throws NoAlertPresentException, which we tell the wait to ignore. Explicit waits are preferred over fixed delays with Thread.Sleep, which make tests slow and flaky.

Console.WriteLine(alert.Text);

alert.Accept();

We print the alert's text and close the dialog.

$ dotnet run
Hello there!

C# Selenium JavaScript and screenshots

Selenium can execute JavaScript code in the page and take screenshots.

Program.cs
using OpenQA.Selenium;
using OpenQA.Selenium.Edge;

var options = new EdgeOptions();
options.AddArgument("--headless=new");
options.AddArgument("--window-size=1024,768");

using var driver = new EdgeDriver(options);

driver.Navigate().GoToUrl("https://example.com");

var js = (IJavaScriptExecutor) driver;

var title = js.ExecuteScript("return document.title;");
var state = js.ExecuteScript("return document.readyState;");
Console.WriteLine($"title: {title}, state: {state}");

js.ExecuteScript("document.body.style.background = 'lightyellow';");

Screenshot shot = driver.GetScreenshot();
shot.SaveAsFile("example.png");

Console.WriteLine("Screenshot saved");

We read two values from the page, change its background colour, and save a screenshot.

options.AddArgument("--window-size=1024,768");

We set the size of the browser window; it determines the size of the screenshot.

var js = (IJavaScriptExecutor) driver;

var title = js.ExecuteScript("return document.title;");

The ExecuteScript method runs JavaScript code in the context of the current page. The value of the return statement is converted to a .NET object.

Screenshot shot = driver.GetScreenshot();
shot.SaveAsFile("example.png");

We take a screenshot of the visible part of the page and save it as a PNG file.

$ dotnet run
title: Example Domain, state: complete
Screenshot saved

C# Selenium testing paragraph contents

Selenium is usually used together with a testing framework. In the next example, we test the contents of two paragraphs with NUnit.

$ dotnet new nunit -o WebTests
$ cd WebTests
$ dotnet add package Selenium.WebDriver

We create an NUnit test project and add Selenium.

pages/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My html page</title>
</head>
<body>

    <p>
        Today is a beautiful day. We go swimming and fishing.
    </p>

    <p>
         Hello there. How are you?
    </p>

</body>
</html>

This is the page under test.

WebTests.csproj
<ItemGroup>
  <None Include="pages/**" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

In the project file, we copy the pages directory to the output directory, where the tests run.

ParagraphTests.cs
using OpenQA.Selenium;
using OpenQA.Selenium.Edge;

namespace WebTests;

public class ParagraphTests
{
    private IWebDriver driver = null!;

    [SetUp]
    public void Setup()
    {
        var options = new EdgeOptions();
        options.AddArgument("--headless=new");

        driver = new EdgeDriver(options);
    }

    [Test]
    public void TestParagraphs()
    {
        var path = Path.Combine(TestContext.CurrentContext.TestDirectory,
            "pages", "index.html");
        driver.Navigate().GoToUrl(new Uri(path).AbsoluteUri);

        var paras = driver.FindElements(By.TagName("p"));

        Assert.That(driver.Title, Is.EqualTo("My html page"));
        Assert.That(paras, Has.Count.EqualTo(2));
        Assert.That(paras[0].Text, Does.Contain("Today is a beautiful day."));
        Assert.That(paras[1].Text, Does.Contain("How are you?"));
    }

    [TearDown]
    public void TearDown() => driver.Dispose();
}

A new browser is started before each test in the SetUp method and closed in the TearDown method.

var path = Path.Combine(TestContext.CurrentContext.TestDirectory,
    "pages", "index.html");

The TestDirectory property contains the directory of the test assembly.

var paras = driver.FindElements(By.TagName("p"));

The FindElements method returns all matching elements. Unlike FindElement, it does not throw an exception when nothing is found; it returns an empty collection.

Assert.That(paras[0].Text, Does.Contain("Today is a beautiful day."));

The Text property returns the visible text of the element, with leading and trailing whitespace removed.

C# Selenium submit form

In the next test, we fill in and submit a form. We use the test form of the httpbin.org service, which echoes the submitted data back as JSON.

FormTests.cs
using OpenQA.Selenium;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Support.UI;

namespace WebTests;

public class FormTests
{
    private IWebDriver driver = null!;

    [SetUp]
    public void Setup()
    {
        var options = new EdgeOptions();
        options.AddArgument("--headless=new");

        driver = new EdgeDriver(options);
    }

    [Test]
    public void TestSubmitForm()
    {
        driver.Navigate().GoToUrl("https://httpbin.org/forms/post");

        driver.FindElement(By.Name("custname")).SendKeys("John Doe");
        driver.FindElement(By.Name("custemail")).SendKeys("john.doe@example.com");
        driver.FindElement(By.CssSelector("input[name='size'][value='medium']")).Click();
        driver.FindElement(By.CssSelector("input[value='cheese']")).Click();
        driver.FindElement(By.Name("comments")).SendKeys("Ring twice");

        driver.FindElement(By.TagName("button")).Click();

        var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
        wait.Until(d => d.Url.EndsWith("/post"));

        var content = driver.FindElement(By.TagName("body")).Text;
        TestContext.Out.WriteLine(content);

        Assert.That(content, Does.Contain("\"custname\": \"John Doe\""));
        Assert.That(content, Does.Contain("\"size\": \"medium\""));
        Assert.That(content, Does.Contain("\"topping\": \"cheese\""));
    }

    [TearDown]
    public void TearDown() => driver.Dispose();
}

We fill in text fields, select a radio button and a check box, and submit the form.

driver.FindElement(By.Name("custname")).SendKeys("John Doe");

We locate the input box by its name attribute and type the text into it with SendKeys.

driver.FindElement(By.CssSelector("input[name='size'][value='medium']")).Click();
driver.FindElement(By.CssSelector("input[value='cheese']")).Click();

Radio buttons and check boxes are selected by clicking them. We locate them with CSS selectors.

driver.FindElement(By.TagName("button")).Click();

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(d => d.Url.EndsWith("/post"));

We click on the submit button and wait until the browser navigates to the response page.

var content = driver.FindElement(By.TagName("body")).Text;
TestContext.Out.WriteLine(content);

We get the text of the response. The output written to TestContext.Out is shown when we run the tests with detailed verbosity.

Assert.That(content, Does.Contain("\"custname\": \"John Doe\""));

The assertions check that the server received the submitted values.

$ dotnet test
...
Passed!  - Failed:     0, Passed:     2, Skipped:     0, Total:     2, Duration: 1 s - WebTests.dll (net10.0)

Both tests pass. With dotnet test --logger "console;verbosity=detailed", we can also see the JSON returned by httpbin.org.

Source

Selenium WebDriver documentation

In this article we have worked with the Selenium web testing framework in C#.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.