Download the right ChromeDriver version & keep it up to date on Windows/Linux/macOS using C# .NET
Tags: dotnet
Published: 2021-03-14 23:08:25

---
title: Download the right ChromeDriver version & keep it up to date on Windows/Linux/macOS using C# .NET
published: true
date: 2021-03-07 00:00:00 UTC
tags: dotnet
canonical_url: https://swimburger.net/blog/dotnet/download-the-right-chromedriver-version-and-keep-it-up-to-date-on-windows-linux-macos-using-csharp-dotnet
cover_image: https://swimburger.net/media/u4hcdhoq/chrome-csharp-social.png?mode=crop&width=1000&height=420
---

You can run automated UI browser tests using technologies like  **Selenium**. The UI testing technology will communicate with a " **webdriver**" which will, in turn, drive around the browser.   
**ChromeDriver**  is the webdriver implementation for  **Google Chrome**. ChromeDriver and Selenium work together very well, but given enough time you will run into the following error:

```
Unhandled exception. System.InvalidOperationException: session not created: This version of ChromeDriver only supports Chrome version 74
  (Driver info: chromedriver=74.0.3729.6 (255758eccf3d244491b8a1317aa76e1ce10d57e9-refs/branch-heads/3729@{#29}),platform=Windows NT 10.0.19042 x86_64) (SessionNotCreated)
   at OpenQA.Selenium.Remote.RemoteWebDriver.UnpackAndThrowOnError(Response errorResponse)
   at OpenQA.Selenium.Remote.RemoteWebDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Remote.RemoteWebDriver.StartSession(ICapabilities desiredCapabilities)
   at OpenQA.Selenium.Remote.RemoteWebDriver..ctor(ICommandExecutor commandExecutor, ICapabilities desiredCapabilities)
   at OpenQA.Selenium.Chrome.ChromeDriver..ctor(ChromeDriverService service, ChromeOptions options, TimeSpan commandTimeout)
   at OpenQA.Selenium.Chrome.ChromeDriver..ctor(ChromeOptions options)
   at OpenQA.Selenium.Chrome.ChromeDriver..ctor()
   at SeleniumConsole.Program.Main(String[] args) in C:\Users\niels\source\repos\SeleniumConsole\Program.cs:line 1
```

**Google Chrome is very good about updating very frequently, often leaving the ChromeDriver out of date**. When the  **ChromeDriver is incompatible with the installed version of Google Chrome** , you will run into the error above.  
The fix is pretty simple, go back to the [ChromeDriver website](https://chromedriver.chromium.org/downloads) and download the most recent version. But doing this manually every time Chrome updates will quickly become unmanageable.  
Especially when you run UI tests on multiple servers, on a periodic basis, or inside a continuous integration and deployment pipeline. Even worse, the failure of these tests may be connected to email, SMS, and/or phone alerting systems.

## How to download the correct version of Chrome

Luckily, the ChromeDriver website provides a systematic way of downloading the correct version of the ChromeDriver given a specific version of Google Chrome.   
Here are [the instructions](https://chromedriver.chromium.org/downloads/version-selection) provided:

- First, find out  **which version of Chrome**  you are using. Let's say you have  **Chrome 72.0.3626.81**.
- Take the Chrome version number, remove the last part, and append the result to URL " **https://chromedriver.storage.googleapis.com/LATEST\_RELEASE\_**". For example, with Chrome version  **72.0.3626.81** , you'd get a URL " **https://chromedriver.storage.googleapis.com/LATEST\_RELEASE\_72.0.3626**".
- Use the URL created in the last step to  **retrieve a small file containing the version of ChromeDriver**  to use. For example, the above URL will get your a file containing " **72.0.3626.69**". (The actual number may change in the future, of course.)
- Use the version number retrieved from the previous step to construct the URL to download ChromeDriver. With version 72.0.3626.69, the URL would be " **https://chromedriver.storage.googleapis.com/index.html?path=72.0.3626.69/**".
- After the initial download, it is recommended that you occasionally go through the above process again to see if there are any bug fix releases.

In the above steps, one small detail has been omitted. You have to download the correct file which will work on the operating system (OS) you're using. You will have the following three options to download on the URL determined in the steps above:

- chromedriver\_linux64.zip (for Linux)
- chromedriver\_mac64.zip (for macOS)
- chromedriver\_win32.zip (for Windows)

It's self-explanatory which file you need to download depending on your OS which is why it was probably omitted from the steps.  But you will need to keep this in account to automate this process.

## Install the correct ChromeDriver using C# .NET

With the instructions provided by Google, you can piece together C# code to automate the installation of the ChromeDriver. Here's an example implementation:

```csharp
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

public class ChromeDriverInstaller
{
    private static readonly HttpClient httpClient = new HttpClient
    {
        BaseAddress = new Uri("https://chromedriver.storage.googleapis.com/")
    };

    public Task Install() => Install(null, false);
    public Task Install(string chromeVersion) => Install(chromeVersion, false);
    public Task Install(bool forceDownload) => Install(null, forceDownload);

    public async Task Install(string chromeVersion, bool forceDownload)
    {
        // Instructions from https://chromedriver.chromium.org/downloads/version-selection
        // First, find out which version of Chrome you are using. Let's say you have Chrome 72.0.3626.81.
        if (chromeVersion == null)
        {
            chromeVersion = await GetChromeVersion();
        }

        // Take the Chrome version number, remove the last part, 
        chromeVersion = chromeVersion.Substring(0, chromeVersion.LastIndexOf('.'));

        // and append the result to URL "https://chromedriver.storage.googleapis.com/LATEST\_RELEASE\_". 
        // For example, with Chrome version 72.0.3626.81, you'd get a URL "https://chromedriver.storage.googleapis.com/LATEST\_RELEASE\_72.0.3626".
        var chromeDriverVersionResponse = await httpClient.GetAsync($"LATEST\_RELEASE\_{chromeVersion}");
        if (!chromeDriverVersionResponse.IsSuccessStatusCode)
        {
            if (chromeDriverVersionResponse.StatusCode == HttpStatusCode.NotFound)
            {
                throw new Exception($"ChromeDriver version not found for Chrome version {chromeVersion}");
            }
            else
            {
                throw new Exception($"ChromeDriver version request failed with status code: {chromeDriverVersionResponse.StatusCode}, reason phrase: {chromeDriverVersionResponse.ReasonPhrase}");
            }
        }

        var chromeDriverVersion = await chromeDriverVersionResponse.Content.ReadAsStringAsync();

        string zipName;
        string driverName;
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            zipName = "chromedriver\_win32.zip";
            driverName = "chromedriver.exe";
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            zipName = "chromedriver\_linux64.zip";
            driverName = "chromedriver";
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            zipName = "chromedriver\_mac64.zip";
            driverName = "chromedriver";
        }
        else
        {
            throw new PlatformNotSupportedException("Your operating system is not supported.");
        }

        string targetPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        targetPath = Path.Combine(targetPath, driverName);
        if (!forceDownload && File.Exists(targetPath))
        {
            using var process = Process.Start(
                new ProcessStartInfo
                {
                    FileName = targetPath,
                    ArgumentList = { "--version" },
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                }
            );
            string existingChromeDriverVersion = await process.StandardOutput.ReadToEndAsync();
            string error = await process.StandardError.ReadToEndAsync();
            await process.WaitForExitAsync();
            process.Kill(true);

            // expected output is something like "ChromeDriver 88.0.4324.96 (68dba2d8a0b149a1d3afac56fa74648032bcf46b-refs/branch-heads/4324@{#1784})"
            // the following line will extract the version number and leave the rest
            existingChromeDriverVersion = existingChromeDriverVersion.Split(" ")[1];
            if (chromeDriverVersion == existingChromeDriverVersion)
            {
                return;
            }

            if (!string.IsNullOrEmpty(error))
            {
                throw new Exception($"Failed to execute {driverName} --version");
            }
        }

        // Use the URL created in the last step to retrieve a small file containing the version of ChromeDriver to use. For example, the above URL will get your a file containing "72.0.3626.69". (The actual number may change in the future, of course.)
        // Use the version number retrieved from the previous step to construct the URL to download ChromeDriver. With version 72.0.3626.69, the URL would be "https://chromedriver.storage.googleapis.com/index.html?path=72.0.3626.69/".
        var driverZipResponse = await httpClient.GetAsync($"{chromeDriverVersion}/{zipName}");
        if (!driverZipResponse.IsSuccessStatusCode)
        {
            throw new Exception($"ChromeDriver download request failed with status code: {driverZipResponse.StatusCode}, reason phrase: {driverZipResponse.ReasonPhrase}");
        }

        // this reads the zipfile as a stream, opens the archive, 
        // and extracts the chromedriver executable to the targetPath without saving any intermediate files to disk
        using (var zipFileStream = await driverZipResponse.Content.ReadAsStreamAsync())
        using (var zipArchive = new ZipArchive(zipFileStream, ZipArchiveMode.Read))
        using (var chromeDriverWriter = new FileStream(targetPath, FileMode.Create))
        {
            var entry = zipArchive.GetEntry(driverName);
            using Stream chromeDriverStream = entry.Open();
            await chromeDriverStream.CopyToAsync(chromeDriverWriter);
        }

        // on Linux/macOS, you need to add the executable permission (+x) to allow the execution of the chromedriver
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            using var process = Process.Start(
                new ProcessStartInfo
                {
                    FileName = "chmod",
                    ArgumentList = { "+x", targetPath },
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                }
            );
            string error = await process.StandardError.ReadToEndAsync();
            await process.WaitForExitAsync();
            process.Kill(true);

            if (!string.IsNullOrEmpty(error))
            {
                throw new Exception("Failed to make chromedriver executable");
            }
        }
    }

    public async Task<string> GetChromeVersion()
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            string chromePath = (string)Registry.GetValue("HKEY\_LOCAL\_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe", null, null);
            if (chromePath == null)
            {
                throw new Exception("Google Chrome not found in registry");
            }

            var fileVersionInfo = FileVersionInfo.GetVersionInfo(chromePath);
            return fileVersionInfo.FileVersion;
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            try
            {
                using var process = Process.Start(
                    new ProcessStartInfo
                    {
                        FileName = "google-chrome",
                        ArgumentList = { "--product-version" },
                        UseShellExecute = false,
                        CreateNoWindow = true,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                    }
                );
                string output = await process.StandardOutput.ReadToEndAsync();
                string error = await process.StandardError.ReadToEndAsync();
                await process.WaitForExitAsync();
                process.Kill(true);

                if (!string.IsNullOrEmpty(error))
                {
                    throw new Exception(error);
                }

                return output;
            }
            catch (Exception ex)
            {
                throw new Exception("An error occurred trying to execute 'google-chrome --product-version'", ex);
            }
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            try
            {
                using var process = Process.Start(
                    new ProcessStartInfo
                    {
                        FileName = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
                        ArgumentList = { "--version" },
                        UseShellExecute = false,
                        CreateNoWindow = true,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                    }
                );
                string output = await process.StandardOutput.ReadToEndAsync();
                string error = await process.StandardError.ReadToEndAsync();
                await process.WaitForExitAsync();
                process.Kill(true);

                if (!string.IsNullOrEmpty(error))
                {
                    throw new Exception(error);
                }

                output = output.Replace("Google Chrome ", "");
                return output;
            }
            catch (Exception ex)
            {
                throw new Exception($"An error occurred trying to execute '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome --version'", ex);
            }
        }
        else
        {
            throw new PlatformNotSupportedException("Your operating system is not supported.");
        }
    }
}
```

The `ChromeDriverInstaller` implementation provides two methods:

- `Install`: This method installs the ChromeDriver at the specified path. If there's already a ChromeDriver at the existing path, it will be updated only if the version of the driver to be installed doesn't match the existing driver. The method takes two parameters:  

  - `chromeVersion`: A string to specify which version of Chrome you want a ChromeDriver for. If null, `GetChromeVersion` is used to get the version of Chrome installed on your machine. Defaults to null.
  - `forceDownload`: Pass in true to force update the ChromeDriver even when the same version of the ChromeDriver is already at the expected location. Defaults to false.
- `GetChromeVersion`: This method returns the version of Chrome installed on your machine. If Chrome is not installed, an exception will be thrown.

The `Install` method has some overloads to make the parameters optional. The `ChromeDriverInstaller` implementation supports Windows, Linux, and macOS.   
For this code to compile, you will need to install the ["Microsoft.Win32.Registry" package](https://www.nuget.org/packages/Microsoft.Win32.Registry/). This package is only supported on Windows, but that's okay because the code from this package will only be executed on Windows.

To use the `ChromeDriverInstaller`, simply create a new instance and call the `Install` method. The ChromeDriver class will automatically look for the ChromeDriver binary in the executing assembly folder among other locations.   
So after the ChromeDriver is installed into the executing assembly folder, you can instantiate your ChromeDriver without specifying the location of the ChromeDriver binary.

For example, here is a small console sample that will do the following:

- Print the detected Chrome version
- Install the ChromeDriver
- Ask you for a URL to check
- Print the title found when browsing the URL using ChromeDriver and Selenium

```csharp
using OpenQA.Selenium.Chrome;
using System;
using System.Threading.Tasks;

namespace SeleniumConsole
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            Console.WriteLine("Installing ChromeDriver");

            var chromeDriverInstaller = new ChromeDriverInstaller();

            // not necessary, but added for logging purposes
            var chromeVersion = await chromeDriverInstaller.GetChromeVersion();
            Console.WriteLine($"Chrome version {chromeVersion} detected");

            await chromeDriverInstaller.Install(chromeVersion);
            Console.WriteLine("ChromeDriver installed");

            Console.WriteLine("Enter URL to visit:");
            var url = Console.ReadLine();
            if (string.IsNullOrEmpty(url))
            {
                Console.WriteLine("No URL entered");
                Console.WriteLine("Press any key to exit");
                Console.ReadKey();
                return;
            }

            var chromeOptions = new ChromeOptions();
            chromeOptions.AddArguments("headless");
            using (var chromeDriver = new ChromeDriver(chromeOptions))
            {
                chromeDriver.Navigate().GoToUrl(url);
                Console.WriteLine($"Page title: {chromeDriver.Title}");
            }
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    }
}
```

The output of this console application looks like this:

```
Installing ChromeDriver
Chrome version 88.0.4324.190 detected
ChromeDriver installed
Enter URL to visit:
https://swimburger.net
Starting ChromeDriver 88.0.4324.96 (68dba2d8a0b149a1d3afac56fa74648032bcf46b-refs/branch-heads/4324@{#1784}) on port 59180
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.

DevTools listening on ws://127.0.0.1:59183/devtools/browser/d1f2f48b-1dcb-4a74-8e18-57b088b87ae4
[0303/002653.418:INFO:CONSOLE(220)] "[PWA Builder] Service worker has been registered for scope: https://swimburger.net/", source: https://swimburger.net/ (220)
Page title: Swimburger - .NET, Web, Azure, Umbraco, and more
Press any key to exit
```

**The second time you execute this console application, the ChromeDriver will have been download already and the download step will be skipped.**  
This console application is only one example of how you can use the `ChromeDriverInstaller`. **You can also plug this into your test projects to ensure the latest matching ChromeDriver is always used before running your test suite.**  
This way you avoid the mismatch error mentioned at the beginning of this blog post. You can find the above [sample code on GitHub](https://github.com/Swimburger/DownloadChromeDriverSample).

## Dissecting the code

Let's dissect the code starting off with the `GetChromeVersion` method. Depending on the Operating System (OS), the version of Chrome is determined differently. The OS is determined using `RuntimeInformation.IsOSPlatform`.

```csharp
public async Task<string> GetChromeVersion()
{
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    {
        string chromePath = (string)Registry.GetValue("HKEY\_LOCAL\_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe", null, null);
        if (chromePath == null)
        {
            throw new Exception("Google Chrome not found in registry");
        }

        var fileVersionInfo = FileVersionInfo.GetVersionInfo(chromePath);
        return fileVersionInfo.FileVersion;
    }
```

On Windows, the registry is queried to get the path to the Chrome installation. Using this path, you can get the `FileVersionInfo` which has the version of Chrome stored in the `FileVersion` property.  
If Chrome is not installed, the registry query will return `null` and an exception will be thrown.

```csharp
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
    try
    {
        using var process = Process.Start(
            new ProcessStartInfo
            {
                FileName = "google-chrome",
                ArgumentList = { "--product-version" },
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
            }
        );
        string output = await process.StandardOutput.ReadToEndAsync();
        string error = await process.StandardError.ReadToEndAsync();
        await process.WaitForExitAsync();
        process.Kill(true);

        if (!string.IsNullOrEmpty(error))
        {
            throw new Exception(error);
        }

        return output;
    }
    catch (Exception ex)
    {
        throw new Exception("An error occurred trying to execute 'google-chrome --product-version'", ex);
    }
}
```

On Linux, the `google-chrome` command is expected to be available globally, so you can execute it and pass in the `--product-version` argument to get the version of Chrome returned as output.  
The `Process` APIs are used to execute this command. The version of Chrome will go to the `StandardOutput`. If there's anything in the `StandardError`, an exception is thrown.

```csharp
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
    try
    {
        using var process = Process.Start(
            new ProcessStartInfo
            {
                FileName = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
                ArgumentList = { "--version" },
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
            }
        );
        string output = await process.StandardOutput.ReadToEndAsync();
        string error = await process.StandardError.ReadToEndAsync();
        await process.WaitForExitAsync();
        process.Kill(true);

        if (!string.IsNullOrEmpty(error))
        {
            throw new Exception(error);
        }

        output = output.Replace("Google Chrome ", "");
        return output;
    }
    catch (Exception ex)
    {
        throw new Exception($"An error occurred trying to execute '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome --version'", ex);
    }
}
```

On macOS, the application should be available at "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome". For some reason, the `--product-version` argument is not available on macOS, but you can use the `--version` argument instead.  
The only difference between the two arguments is that the latter will prefix the version with "Google Chrome ".

The `Process` APIs are used to execute this command. The version of Chrome will go to the `StandardOutput` with the prefix which is removed from the string later on. If there's anything in the `StandardError`, an exception is thrown.

```csharp
    else
    {
        throw new PlatformNotSupportedException("Your operating system is not supported.");
    }
}
```

Lastly, if the code isn't run on Windows, Linux, or macOS, an exception is thrown.

The `GetChromeVersion` method will be called from the `Install` method if no version of Chrome is passed in as a parameter.  
Then the last part of the version number is stripped off.

```csharp
public async Task Install(string chromeVersion, bool forceDownload)
{
    // Instructions from https://chromedriver.chromium.org/downloads/version-selection
    // First, find out which version of Chrome you are using. Let's say you have Chrome 72.0.3626.81.
    if (chromeVersion == null)
    {
        chromeVersion = await GetChromeVersion();
    }

    // Take the Chrome version number, remove the last part, 
    chromeVersion = chromeVersion.Substring(0, chromeVersion.LastIndexOf('.'));
```

The ChromeDriver version is requested using the `HttpClient` which is defined as a static field at the top of the class.  
If the response status code is not 200, an exception is thrown. Otherwise, the response content is read and stored in the `chromeDriverVersion` variable.

```csharp
// and append the result to URL "https://chromedriver.storage.googleapis.com/LATEST\_RELEASE\_". 
// For example, with Chrome version 72.0.3626.81, you'd get a URL "https://chromedriver.storage.googleapis.com/LATEST\_RELEASE\_72.0.3626".
var chromeDriverVersionResponse = await httpClient.GetAsync($"LATEST\_RELEASE\_{chromeVersion}");
if (!chromeDriverVersionResponse.IsSuccessStatusCode)
{
    if (chromeDriverVersionResponse.StatusCode == HttpStatusCode.NotFound)
    {
        throw new Exception($"ChromeDriver version not found for Chrome version {chromeVersion}");
    }
    else
    {
        throw new Exception($"ChromeDriver version request failed with status code: {chromeDriverVersionResponse.StatusCode}, reason phrase: {chromeDriverVersionResponse.ReasonPhrase}");
    }
}

var chromeDriverVersion = await chromeDriverVersionResponse.Content.ReadAsStringAsync();
```

Depending on which OS you're on, the correct zip file to download and the name of the file inside the zip will be initialized differently.

```csharp
string zipName;
string driverName;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
    zipName = "chromedriver\_win32.zip";
    driverName = "chromedriver.exe";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
    zipName = "chromedriver\_linux64.zip";
    driverName = "chromedriver";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
    zipName = "chromedriver\_mac64.zip";
    driverName = "chromedriver";
}
else
{
    throw new PlatformNotSupportedException("Your operating system is not supported.");
}
```

The `Install` method does not allow you to specify where to place the ChromeDriver binary. It assumes it should go into the folder where the executing assembly is stored.  
You could add a `targetPath` parameter to allow you to specify where the ChromeDriver should be saved. But the current implementation will save the ChromeDriver binary into the folder of the executing assembly:

```csharp
string targetPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
targetPath = Path.Combine(targetPath, driverName);
```

This section will check if there's an existing ChromeDriver at the target path. No ChromeDriver will be downloaded if the version to be downloaded matches the version of the ChromeDriver on disk. Instead, it will return to the caller.  
If `forceDownload` is true, this optimization will be skipped entirely.

```csharp
if (!forceDownload && File.Exists(targetPath))
{
    using var process = Process.Start(
        new ProcessStartInfo
        {
            FileName = targetPath,
            ArgumentList = { "--version" },
            UseShellExecute = false,
            CreateNoWindow = true,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
        }
    );
    string existingChromeDriverVersion = await process.StandardOutput.ReadToEndAsync();
    string error = await process.StandardError.ReadToEndAsync();
    await process.WaitForExitAsync();
    process.Kill(true);

    // expected output is something like "ChromeDriver 88.0.4324.96 (68dba2d8a0b149a1d3afac56fa74648032bcf46b-refs/branch-heads/4324@{#1784})"
    // the following line will extract the version number and leave the rest
    existingChromeDriverVersion = existingChromeDriverVersion.Split(" ")[1];
    if (chromeDriverVersion == existingChromeDriverVersion)
    {
        return;
    }

    if (!string.IsNullOrEmpty(error))
    {
        throw new Exception($"Failed to execute {driverName} --version");
    }
}
```

The ChromeDriver ZIP file is requested using the `HttpClient` which is declared as a static field.  
The response content is read as a stream and passed to the `ZipArchive` class. Using the `zipArchive` the binary inside the ZIP file is also read as a stream and copied to the `FileStream` used to write the binary to disk.

```csharp
// Use the URL created in the last step to retrieve a small file containing the version of ChromeDriver to use. For example, the above URL will get your a file containing "72.0.3626.69". (The actual number may change in the future, of course.)
// Use the version number retrieved from the previous step to construct the URL to download ChromeDriver. With version 72.0.3626.69, the URL would be "https://chromedriver.storage.googleapis.com/index.html?path=72.0.3626.69/".
var driverZipResponse = await httpClient.GetAsync($"{chromeDriverVersion}/{zipName}");
if (!driverZipResponse.IsSuccessStatusCode)
{
    throw new Exception($"ChromeDriver download request failed with status code: {driverZipResponse.StatusCode}, reason phrase: {driverZipResponse.ReasonPhrase}");
}
        
// this reads the zipfile as a stream, opens the archive, 
// and extracts the chromedriver executable to the targetPath without saving any intermediate files to disk
using (var zipFileStream = await driverZipResponse.Content.ReadAsStreamAsync())
using (var zipArchive = new ZipArchive(zipFileStream, ZipArchiveMode.Read))
using (var chromeDriverWriter = new FileStream(targetPath, FileMode.Create))
{
    var entry = zipArchive.GetEntry(driverName);
    using Stream chromeDriverStream = entry.Open();
    await chromeDriverStream.CopyToAsync(chromeDriverWriter);
}
```

Lastly, only on Linux and macOS, you have to change the file permissions to allow the ChromeDriver to be executed.

```csharp
    // on Linux/macOS, you need to add the executable permission (+x) to allow the execution of the chromedriver
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
    {
        using var process = Process.Start(
            new ProcessStartInfo
            {
                FileName = "chmod",
                ArgumentList = { "+x", targetPath },
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
            }
        );
        string error = await process.StandardError.ReadToEndAsync();
        await process.WaitForExitAsync();
        process.Kill(true);

        if (!string.IsNullOrEmpty(error))
        {
            throw new Exception("Failed to make chromedriver executable");
        }
    }
}
```

## Summary
Google Chrome updates by itself all the time, but the ChromeDriver to run selenium tests does not update automatically alongside it. This leads to incompatibility errors over time.  
You can fix this by updating the ChromeDriver manually which is very cumbersome when you have to do this over and over again. Especially when you use a NuGet package or check-in the ChromeDriver file into source-code, keeping your ChromeDriver in sync with the version of Chrome is painful. You can avoid this by automatically downloading the right ChromeDriver at runtime before running your selenium tests. Using the `ChromeDriverInstaller` class, you can detect the version of Chrome on your machine and automatically download the matching ChromeDriver.

If you prefer using PowerShell, check out "[Download the right ChromeDriver version & keep it up to date on Windows/Linux/macOS using PowerShell](https://swimburger.net/blog/powershell/download-the-right-chromedriver-on-windows-linux-macos-using-powershell)".