Download the right ChromeDriver version & keep it up to date on Windows/Linux/macOS using C# .NET

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 10

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 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 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:

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.

"

); } } }

This code sample is for .NET (Core), you can find the .NET Framework version later on.

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. 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

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.

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.

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.

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.

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.

    

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.

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.

// 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.

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:

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.

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.

// 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.

    

// 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

"

); } } }

Download the right ChromeDriver automatically using .NET Framework

The previous code samples work for .NET (Core) on Windows, macOS, and Linux. Unfortunately, .NET Framework does not have all the convenient .NET APIs and C# features used in the above sample. But if you still need to support it, here’s the .NET Framework version:

using

System;

using

System.Diagnostics;

using

System.IO;

using

System.IO.Compression;

using

System.Net;

using

System.Net.Http;

using

System.Reflection;

using

System.Threading.Tasks;

using

Microsoft.Win32;

namespace

SeleniumConsoleFramework {

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 = 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 =

"

chromedriver_win32.zip

"

;

string

driverName =

"

chromedriver.exe

"

;

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, Arguments =

"

--version

"

, UseShellExecute =

false

, CreateNoWindow =

true

, RedirectStandardOutput =

true

, RedirectStandardError =

true

, } )) {

string

existingChromeDriverVersion = await process.StandardOutput.ReadToEndAsync();

string

error = await process.StandardError.ReadToEndAsync(); process.WaitForExit(); process.Kill();

// 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); } } }

public

string

GetChromeVersion() {

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; } } }

The code is a lot smaller because you don’t have to consider any operating system but Windows.

Using the .NET Framework version using a console app would look like this:

using

System;

using

OpenQA.Selenium.Chrome;

namespace

SeleniumConsoleFramework {

public

class

Program {

public

static

void

Main(

string

[] args) { Console.WriteLine(

"

Installing ChromeDriver

"

);

var

chromeDriverInstaller =

new

ChromeDriverInstaller();

// not necessary, but added for logging purposes

var

chromeVersion = chromeDriverInstaller.GetChromeVersion(); Console.WriteLine($

"

Chrome version {chromeVersion} detected

"

); 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(); } } }

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.

You can find both the .NET (Core) and .NET Framework samples on this GitHub repository.

If you prefer using PowerShell, check out “Download the right ChromeDriver version & keep it up to date on Windows/Linux/macOS using PowerShell”.

Xổ số miền Bắc