Tutorials

Tutorials

Downloading and Patching a Browser Driver Automatically

Selenium-based engines need a matching driver executable (chromedriver, msedgedriver, geckodriver) on disk. This tutorial uses GPAL to detect the installed browser version, find the matching driver download on the vendor's release page, download it, and unzip the driver into your drivers folder.

Complete Program

Here's the whole workflow, start to finish. Each piece is broken down and explained below.

using GenerallyPositive;

using GenerallyPositive.Browser;

using static GenerallyPositive.Enums;

GPAL.WithPublishToDebug().WithPublishToConsole();

Selector driverLinkSelector = (Selector)GPAL.Selector

.WithXPath("//a[contains(@href, 'geckodriver') and contains(@href, 'geckodriver-v0.36.0-win32.zip')]")

.CallIfNotFound(CallIfNotFound)

.CallIfFound(CallIfFound)

.WithStopOnNotFound(true);

IBrowser browser = GPAL.Browser

.WithBrowserType(BrowserType.FireFox)

.WithUseAutomationEngine(AutomationEngine.Selenium)

.WithOpenPDFExternally(false)

.WithDriverLocation(@"c:drivers")

.ToGPALObject();

browser

.GoTo("https://example.com/mozilla/geckodriver/releases")

.WithSelector(driverLinkSelector)

.WithAllThatMatch();

browser.StartWorkflow();

Find the Right Driver Link on the Release Page

The selector looks for an anchor whose href contains both 'geckodriver' and the specific release filename you want. CallIfFound and CallIfNotFound register the callbacks GPAL will invoke once it knows whether that link exists, and WithStopOnNotFound(true) halts the workflow if it doesn't.

Selector driverLinkSelector = (Selector)GPAL.Selector

.WithXPath("//a[contains(@href, 'geckodriver') and contains(@href, 'geckodriver-v0.36.0-win32.zip')]")

.CallIfNotFound(CallIfNotFound)

.CallIfFound(CallIfFound)

.WithStopOnNotFound(true);

TIP

Hard-coding the release filename (here geckodriver-v0.36.0-win32.zip) keeps the download reproducible. Swap in chromedriver-win64.zip or msedgedriver.zip patterns for Chrome or Edge, matched against the locally installed browser version.

Navigate, Select, and Start the Workflow

GoTo loads the releases page, WithSelector queues the driver-link selector, and WithAllThatMatch tells GPAL to evaluate every match. StartWorkflow runs the queued steps and fires CallIfFound or CallIfNotFound based on what was found.

browser

.GoTo("https://example.com/mozilla/geckodriver/releases")

.WithSelector(driverLinkSelector)

.WithAllThatMatch();

browser.StartWorkflow();

CallIfFound: Download and Extract the Driver

Once the link is found, CallIfFound reads the href off the matched element, closes the current browser (since the driver file it's about to replace may be in use), downloads the zip, and extracts the driver executable into the drivers folder.

public static CallIfStatus CallIfFound(IBrowser browser, List<GPALElement> foundElements,

List<GPALElement> matchedElements, Selector selector, bool matchedAll)

{

string path = @"c:drivers";

string filename = "geckodriver.zip";

string driver = "geckodriver.exe";

GPALElement webElement = matchedElements[0];

string href = webElement.GetAttribute("href");

browser.Close(true); // release the driver file we are about to replace

System.Threading.Thread.Sleep(2_000);

using (var webClient = new System.Net.WebClient())

webClient.DownloadFile(href, path + filename);

using (var archive = System.IO.Compression.ZipFile.OpenRead(path + filename))

{

var entry = archive.Entries.FirstOrDefault(e =>

e.Name.Equals(driver, StringComparison.OrdinalIgnoreCase));

if (entry != null)

{

string destination = System.IO.Path.Combine(path, entry.Name);

if (System.IO.File.Exists(destination))

System.IO.File.Delete(destination);

entry.ExtractToFile(destination);

}

}

System.IO.File.Delete(path + filename);

return CallIfStatus.Handled;

}

WARNING

On Windows, the driver executable is locked while its browser session is running. Call Close(true) and give the process a moment to exit before downloading a new copy over it.

CallIfNotFound: Fail Loudly

If the release link can't be found - the page layout changed, or the version string no longer matches - CallIfNotFound shows a message and returns CallIfStatus.Terminate, stopping the workflow rather than continuing with a missing driver.

public static CallIfStatus CallIfNotFound(IBrowser browser, List<GPALElement> foundElements,

List<GPALElement> matchedElements, Selector selector, bool matchedAll)

{

MessageBox.Show($"Unable to find {selector.Name}");

return CallIfStatus.Terminate;

}