paint-brush
How to Build a PowerShell Script to Keep ChromeDriver Up to Dateby@swimburger
567 reads
567 reads

How to Build a PowerShell Script to Keep ChromeDriver Up to Date

by Niels SwimbergheMarch 4th, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Chrome frequently updates automatically causing ChromeDriver versions to mismatch. Using PowerShell you can download the correct version of the ChromeDriver

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - How to Build a PowerShell Script to Keep ChromeDriver Up to Date
Niels Swimberghe HackerNoon profile picture

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.

An analogy: Consumers without a driver's license use a taxi to go to a specific location. The consumers don't know how to drive themselves, and instead provide the desired location to the driver. The taxi driver knows how to drive the vehicle towards the instructed location. In this analogy, the consumers are your UI testing code instructing the webdriver, the taxi driver is the webdriver, and the web browser is the vehicle.

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 updates 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 after 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 ChromeDriver

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

Build a PowerShell script to install the correct ChromeDriver

Let's build this PowerShell script step by step based on the instructions provided by Google. If you just want to use the end result, feel free to scroll to skip this section.

First, you need to determine the version of Chrome installed on your local machine. This is arguably the hardest step because this is different for every OS installs software differently. There are also multiple ways to determine the version, some more robust than others.

On Windows, you can query the registry for the location of Chrome and then get the version stored inside the 

VersionInfo
 property.

(Get-Item (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe').'(Default)').VersionInfo.FileVersion

To properly handle the scenario where Google Chrome isn't installed, you should surround this line with a TryCatch block and also specify 

-ErrorAction Stop
 to ensure errors will trigger the Catch block.

Try{
    (Get-Item (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe' -ErrorAction Stop).'(Default)').VersionInfo.FileVersion;
}Catch{
    Throw "Google Chrome not found in registry";
}

Lastly, you only want to run this on Windows, so you should surround this code with the following If-block:

# $IsWindows will PowerShell Core but not on PowerShell 5 and below, but $Env:OS does
# this way you can safely check whether the current machine is running Windows pre and post PowerShell Core
If ($IsWindows -or $Env:OS) {

}

On Linux, the command google-chrome should be globally available when installed. Use the command to get the version using --product-version argument.

To handle the scenario where Chrome isn't installed, you test the availability of the command before running it like this:

Try{
    # this will check whether google-chrome command is available
    Get-Command google-chrome -ErrorAction Stop | Out-Null;
    google-chrome --product-version;
}Catch{
    Throw "'google-chrome' command not found";
}

Ensure these commands only run on Linux, using the following ElseIf-block:

ElseIf ($IsLinux) {
    
}

On macOS, you can test if the application has been install at the expected location. If it is, execute the application with the 

--version
 argument.
In this case, the Chrome application will prefix the version with "Google Chrome ". Remove the prefix using the string replace method.

$ChromePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
If (Test-Path $ChromePath) {
    $Version = & $ChromePath --version;
    $Version = $Version.Replace("Google Chrome ", "");
    $Version;
}
Else {
    Throw "Google Chrome not found on your MacOS machine";
}

Ensure these commands only run on Linux, using the following ElseIf-block:

ElseIf ($IsMacOS) {

}

PowerShell isn't supported on any other operating systems, but just in case add the following Else-block:

Else {
    Throw "Your operating system is not supported by this script.";
}

Put the code together and wrap it in a function like this:

Function Get-ChromeVersion {
    # $IsWindows will PowerShell Core but not on PowerShell 5 and below, but $Env:OS does
    # this way you can safely check whether the current machine is running Windows pre and post PowerShell Core
    If ($IsWindows -or $Env:OS) {
        Try {
            (Get-Item (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe' -ErrorAction Stop).'(Default)').VersionInfo.FileVersion;
        }
        Catch {
            Throw "Google Chrome not found in registry";
        }
    }
    ElseIf ($IsLinux) {
        Try {
            # this will check whether google-chrome command is available
            Get-Command google-chrome -ErrorAction Stop | Out-Null;
            google-chrome --product-version;
        }
        Catch {
            Throw "'google-chrome' command not found";
        }
    }
    ElseIf ($IsMacOS) {
        $ChromePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
        If (Test-Path $ChromePath) {
            $Version = & $ChromePath --version;
            $Version = $Version.Replace("Google Chrome ", "");
            $Version;
        }
        Else {
            Throw "Google Chrome not found on your MacOS machine";
        }
    }
    Else {
        Throw "Your operating system is not supported by this script.";
    }
}

In PowerShell, when a value isn't captured, it is returned to the consumer of the function. (Except when the value is piped to Out-Null).

After the Function declaration, put the following lines of code to invoke the function, capture the chrome version, and print it to the output:

# 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.
$ChromeVersion = Get-ChromeVersion -ErrorAction Stop;
Write-Output "Google Chrome version $ChromeVersion found on machine";

(The comments are the steps provided by the ChromeDriver website.)

Remove the last part of the version, query the latest matching release ChromeDriver version from the ChromeDriver website, and print the ChromeDriver version to the output:

#   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".
$ChromeDriverVersion = (Invoke-WebRequest "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$ChromeVersion").Content;
Write-Output "Latest matching version of Chrome Driver is $ChromeDriverVersion";

Create a temporary file and store the path in a variable 

$TempFilePath
. Change the tmp-extension to the zip-extension.

Lastly, create another variable 

$TempFileUnzipPath
 to store the folder path and simply use the temporary file path without extension.

You will need to 

$TempFileUnzipPath
 to specify where to unzip the ZIP-file later.

$TempFilePath = [System.IO.Path]::GetTempFileName();
$TempZipFilePath = $TempFilePath.Replace(".tmp", ".zip");
Rename-Item -Path $TempFilePath -NewName $TempZipFilePath;
$TempFileUnzipPath = $TempFilePath.Replace(".tmp", "");

Depending on the OS, download the correct ZIP-file from the ChromeDriver website, unzip it using Expand-Archive and move the driver to the desired location:

#   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/".
If ($IsWindows -or $Env:OS) {
    Invoke-WebRequest "https://chromedriver.storage.googleapis.com/$ChromeDriverVersion/chromedriver_win32.zip" -OutFile $TempZipFilePath;
    Expand-Archive $TempZipFilePath -DestinationPath $TempFileUnzipPath;
    Move-Item "$TempFileUnzipPath/chromedriver.exe" -Destination "path/to/save/chromedriver.exe" -Force;
}
ElseIf ($IsLinux) {
    Invoke-WebRequest "https://chromedriver.storage.googleapis.com/$ChromeDriverVersion/chromedriver_linux64.zip" -OutFile $TempZipFilePath;
    Expand-Archive $TempZipFilePath -DestinationPath $TempFileUnzipPath;
    Move-Item "$TempFileUnzipPath/chromedriver" -Destination "path/to/save/chromedriver" -Force;
}
ElseIf ($IsMacOS) {
    Invoke-WebRequest "https://chromedriver.storage.googleapis.com/$ChromeDriverVersion/chromedriver_mac64.zip" -OutFile $TempZipFilePath;
    Expand-Archive $TempZipFilePath -DestinationPath $TempFileUnzipPath;
    Move-Item "$TempFileUnzipPath/chromedriver" -Destination "path/to/save/chromedriver" -Force;
}
Else {
    Throw "Your operating system is not supported by this script.";
}

Note: You should update the "path/to/save/chromedriver" hardcoded string to the desired location or ideally a variable. See the end result script to accept the desired path as a parameter.

Lastly, clean up by deleting the temporary zip-file and folder:

# Clean up temp files
Remove-Item $TempZipFilePath;
Remove-Item $TempFileUnzipPath -Recurse;

Executing all this code will download the latest ChromeDriver matching the installed instance of Chrome. 

End result: Enhanced PowerShell script to download ChromeDriver

The code so far has solely focused on executing the instructions provided by Google to keep things easy to digest and streamlined.
The end result below will run the same commands, but the adjustments make the script more reusable, flexible, and performant. Here's a list of changes:

  • Add parameters:
  • ChromeDriverOutputPath: The path to save the ChromeDriver to
  • ChromeVersion: Specify the version of Chrome you need a ChromeDriver for. Use this parameter in case you want to download a ChromeDriver for a different version of Chrome than the one installed.
    You can also use this parameter to specify the correct version yourself instead of letting the script detect the local Chrome installation.
  • ForceDownload: Download the latest ChromeDriver regardless of whether the ChromeDriver at
    $ChromeDriverOutputPath
    is the same version.
  • A check was added to prevent unnecessarily download the ChromeDriver if the same version of ChromeDriver is already present at the path specified using $ChromeDriverOutputPath. You can ignore this check by specifying the
    -ForceDownload 
    switch.
  • By default, PowerShell shows a progress bar when downloading files using Invoke-WebRequest. This progress bar can decrease the download performance by a lot.

Here's the final result:

[CmdletBinding()]
param (
    [Parameter(Mandatory = $True)]
    [string]
    $ChromeDriverOutputPath,    
    [Parameter(Mandatory = $false)]
    [string]
    $ChromeVersion, 
    [Parameter(Mandatory = $false)]
    [Switch]
    $ForceDownload
)

# store original preference to revert back later
$OriginalProgressPreference = $ProgressPreference;
# setting progress preference to silently continue will massively increase the performance of downloading the ChromeDriver
$ProgressPreference = 'SilentlyContinue';

Function Get-ChromeVersion {
    # $IsWindows will PowerShell Core but not on PowerShell 5 and below, but $Env:OS does
    # this way you can safely check whether the current machine is running Windows pre and post PowerShell Core
    If ($IsWindows -or $Env:OS) {
        Try {
            (Get-Item (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe' -ErrorAction Stop).'(Default)').VersionInfo.FileVersion;
        }
        Catch {
            Throw "Google Chrome not found in registry";
        }
    }
    ElseIf ($IsLinux) {
        Try {
            # this will check whether google-chrome command is available
            Get-Command google-chrome -ErrorAction Stop | Out-Null;
            google-chrome --product-version;
        }
        Catch {
            Throw "'google-chrome' command not found";
        }
    }
    ElseIf ($IsMacOS) {
        $ChromePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
        If (Test-Path $ChromePath) {
            $Version = & $ChromePath --version;
            $Version = $Version.Replace("Google Chrome ", "");
            $Version;
        }
        Else {
            Throw "Google Chrome not found on your MacOS machine";
        }
    }
    Else {
        Throw "Your operating system is not supported by this script.";
    }
}

# 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 ([string]::IsNullOrEmpty($ChromeVersion)) {
    $ChromeVersion = Get-ChromeVersion -ErrorAction Stop;
    Write-Output "Google Chrome version $ChromeVersion found on machine";
}

#   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".
$ChromeDriverVersion = (Invoke-WebRequest "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$ChromeVersion").Content;
Write-Output "Latest matching version of Chrome Driver is $ChromeDriverVersion";

If (($ForceDownload -eq $False) -and (Test-path $ChromeDriverOutputPath)) {
    #ChromeDriver 88.0.4324.96 (68dba2d8a0b149a1d3afac56fa74648032bcf46b-refs/branch-heads/4324@{#1784})
    $ExistingChromeDriverVersion = & $ChromeDriverOutputPath --version;
    $ExistingChromeDriverVersion = $ExistingChromeDriverVersion.Split(" ")[1];
    If ($ChromeDriverVersion -eq $ExistingChromeDriverVersion) {
        Write-Output "Chromedriver on machine is already latest version. Skipping.";
        Write-Output "Use -ForceDownload to reinstall regardless";
        Exit;
    }
}

$TempFilePath = [System.IO.Path]::GetTempFileName();
$TempZipFilePath = $TempFilePath.Replace(".tmp", ".zip");
Rename-Item -Path $TempFilePath -NewName $TempZipFilePath;
$TempFileUnzipPath = $TempFilePath.Replace(".tmp", "");
#   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/".

If ($IsWindows -or $Env:OS) {
    Invoke-WebRequest "https://chromedriver.storage.googleapis.com/$ChromeDriverVersion/chromedriver_win32.zip" -OutFile $TempZipFilePath;
    Expand-Archive $TempZipFilePath -DestinationPath $TempFileUnzipPath;
    Move-Item "$TempFileUnzipPath/chromedriver.exe" -Destination $ChromeDriverOutputPath -Force;
}
ElseIf ($IsLinux) {
    Invoke-WebRequest "https://chromedriver.storage.googleapis.com/$ChromeDriverVersion/chromedriver_linux64.zip" -OutFile $TempZipFilePath;
    Expand-Archive $TempZipFilePath -DestinationPath $TempFileUnzipPath;
    Move-Item "$TempFileUnzipPath/chromedriver" -Destination $ChromeDriverOutputPath -Force;
}
ElseIf ($IsMacOS) {
    Invoke-WebRequest "https://chromedriver.storage.googleapis.com/$ChromeDriverVersion/chromedriver_mac64.zip" -OutFile $TempZipFilePath;
    Expand-Archive $TempZipFilePath -DestinationPath $TempFileUnzipPath;
    Move-Item "$TempFileUnzipPath/chromedriver" -Destination $ChromeDriverOutputPath -Force;
}
Else {
    Throw "Your operating system is not supported by this script.";
}

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

# Clean up temp files
Remove-Item $TempZipFilePath;
Remove-Item $TempFileUnzipPath -Recurse;

# reset back to original Progress Preference
$ProgressPreference = $OriginalProgressPreference;

Save this code to a file named 'InstallChromeDriver.ps1' and invoke the script from PowerShell like this:

.\InstallChromeDriver.ps1 -ChromeDriverOutputPath .\chromedriver.exe

If you want to specify the version of Chrome yourself, add the 

-ChromeVersion
 parameter:

.\InstallChromeDriver.ps1 -ChromeDriverOutputPath .\chromedriver.exe -ChromeVersion 88.0.4324.182

If you want to force the download of the driver even if the same version is already downloaded at the specified location, add the 

-ForceDownload
 parameter:

.\InstallChromeDriver.ps1 -ChromeDriverOutputPath .\chromedriver.exe -ForceDownload

I recommend you run this script on a regular basis or right before running your UI tests to keep your ChromeDriver up-to-date. Just make sure that the ChromeDriver isn't being locked by one of your UI tests when you're trying to update it.

If you have any issues, feedback, or suggestions, feel free to reach out to me on Twitter or comment on this GitHub gist.

Also published on: https://swimburger.net/blog/powershell/download-the-right-chromedriver-on-windows-linux-macos-using-powershell