Matching ChromeDriver NuGet package with Chrome version

I got tired of a Selenium quality control xUnit test project often breaking because of Chrome auto-updating and looked around for a solution.

I found a nice cross platform GetChromeVersion() function in a blog post from Niels Swimberghe that became my starting point.

I then figured a good way would be to match the installed version of Selenium.WebDriver.ChromeDriver with the correct version number for the NuGet package. I had learned that they always share the first three version number parts. To call the nuget.org API I installed the package NuGet.Protocol.

To get the machine's Chrome to match with the NuGet package version in the test project I opted to use a regular console application and run that first and just edit the version number and overwrite the CSPROJ-file. I figured this would be a nice way that works without hassle on both build machines and locally.

I thought about running a dotnet install package command with the a fixed version as parameter but this mimics what the last 30 commits to the repo looks like, and I can do everything to get set up from the same place.

using System.Text;
using System.Xml.Linq;
using NuGet.Common;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;

// Call the function from Niels Swimberghe's post
var installed = await new ChromeHelper().GetChromeVersion();

Console.WriteLine(installed);

var splitInstalled = installed.Split(".".ToCharArray(), StringSplitOptions.None);

var logger = NullLogger.Instance;
var cancellationToken = CancellationToken.None;
var cache = new SourceCacheContext();
var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");
var resource = await repository.GetResourceAsync<FindPackageByIdResource>();

var versions = await resource.GetAllVersionsAsync(
    "Selenium.WebDriver.ChromeDriver",
    cache,
    logger,
    cancellationToken);

var bestMatch = string.Empty;

foreach (var version in versions.Reverse().Where(x => !x.IsPrerelease))
{
    Console.WriteLine($"Found version {version}");

    var splitPackage = version.ToString().Split(".".ToCharArray(), StringSplitOptions.None);

    var match = true;

    for (var i = 0; i < 3; i++)
    {
        if (splitInstalled[i] != splitPackage[i])
        {
            match = false;
        }
    }

    if (match)
    {
        bestMatch = version.ToString();
        Console.WriteLine($"Latest matching package {bestMatch}");
        break;
    }
}

Console.WriteLine(Environment.CurrentDirectory);

var di = new DirectoryInfo(Environment.CurrentDirectory);

// This of course depends on solution folder structure
while (di.Exists)
{
    di = di.Parent;

    if (di!.Name == "src")
    {
        var sln = di.Parent;
        var testsCsProjPath = Path.Combine(sln!.FullName, "tests", "Checker.Tests", "Checker.Tests.csproj");
        var testsCsProj = new FileInfo(testsCsProjPath);

        if (testsCsProj.Exists)
        {
            using TextReader reader = File.OpenText(testsCsProj.FullName);
            var document = await XDocument.LoadAsync(reader, LoadOptions.PreserveWhitespace, cancellationToken);
            reader.Close();
            reader.Dispose();

            var elements = document
                .Descendants("PackageReference")
                .Where(x => x.Attribute("Include")?.Value == "Selenium.WebDriver.ChromeDriver")
                .ToList();

            elements.First().Attribute("Version")!.Value = bestMatch;

            File.WriteAllText(testsCsProjPath, document.ToString(), Encoding.UTF8);
        }

        break;
    }
}

It's of course important to build and run this Checker.Setup console app project first and on its own to avoid file locks. So in my structure I can go with this sequence.

cd src\Checker.Setup
dotnet run
cd ..
cd ..
dotnet build
dotnet test

Now the Checker solution needs maintenance much less frequently. All good.

Comments?

Published and tagged with these categories: .NET