如何在硒中自动更新 chromeDriver 和 geckDriver

sje*_*ani 9 selenium selenium-grid selenium-chromedriver geckodriver

我在多个节点机器上设置了 selenium 网格,其中我在所有 selenium 节点机器上手动下载 chromeDriver 和 geckoDriver,并将它们分别用于 chrome 和 firefox 浏览器。

现在这里的 chrome 和 firefox 浏览器(在所有 selenium 节点机器上)设置为“自动更新”(这是必需的,因为我希望我的应用程序始终在最新的浏览器版本上进行测试),因为我的节点机器上的浏览器不断更新更常见的是,因为相应的驱动程序更新是一个手动过程,它迫使我登录到每个 selenium 节点机器并手动更新它们

这个过程可以自动化吗?

PS:我知道 dockerized selenium grid 可用于获取/拉取最新的浏览器图像及其驱动程序,但是从传统 selenium grid 切换到 dockerized selenium grid 是另一回事,需要一些时间来实现。

Dav*_*ers 9

首先@Asyranok是对的,即使实现自动更新代码也不会100%工作。然而,对于我们许多人来说,偶尔的停机是“可以的”,只要只是几天。

我发现每隔几个月手动更新 X 服务器非常令人恼火,虽然 selenium 网站上有关于如何“自动更新”驱动程序的详细说明,但我还没有看到一种公开可用的非库实现本指南。

我的答案是针对 C# 的,对于这种语言,通常建议的解决方案是使用NuGet自动提取最新的驱动程序,这有两个问题:

  1. 您需要以 chrome 更新的频率进行部署(大多数公司还没有做到这一点,我们也没有),否则您的应用程序将在 chrome 更新和应用程序的“新”版本部署之间的时间内“损坏”,然后再次这仅适用于按计划发布的情况,如果您临时发布,则必须执行一系列手动步骤来更新、构建、发布等,以使应用程序再次运行。

  2. 您需要(通常无需解决方法)手动从 NuGet 中提取最新的 chromedrive,这也是一个手动过程。

最好的是 python 有什么,@leminhnguyenHUST 建议使用一个库,该库会在运行时自动拉取最新的 chromedriver。我环顾四周,还没有找到任何 C# 可以做到这一点,所以我决定推出自己的并将其构建到我的应用程序中:

public void DownloadLatestVersionOfChromeDriver()
{
    string path = DownloadLatestVersionOfChromeDriverGetVersionPath();
    var version = DownloadLatestVersionOfChromeDriverGetChromeVersion(path);
    var urlToDownload = DownloadLatestVersionOfChromeDriverGetURLToDownload(version);
    DownloadLatestVersionOfChromeDriverKillAllChromeDriverProcesses();
    DownloadLatestVersionOfChromeDriverDownloadNewVersionOfChrome(urlToDownload);
}

public string DownloadLatestVersionOfChromeDriverGetVersionPath()
{
    //Path originates from here: https://chromedriver.chromium.org/downloads/version-selection            
    using (RegistryKey key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe"))
    {
        if (key != null)
        {
            Object o = key.GetValue("");
            if (!String.IsNullOrEmpty(o.ToString()))
            {
                return o.ToString();
            }
            else
            {
                throw new ArgumentException("Unable to get version because chrome registry value was null");
            }
        }
        else
        {
            throw new ArgumentException("Unable to get version because chrome registry path was null");
        }
    }
}

public string DownloadLatestVersionOfChromeDriverGetChromeVersion(string productVersionPath)
{
    if (String.IsNullOrEmpty(productVersionPath))
    {
        throw new ArgumentException("Unable to get version because path is empty");
    }

    if (!File.Exists(productVersionPath))
    {
        throw new FileNotFoundException("Unable to get version because path specifies a file that does not exists");
    }

    var versionInfo = FileVersionInfo.GetVersionInfo(productVersionPath);
    if (versionInfo != null && !String.IsNullOrEmpty(versionInfo.FileVersion))
    {
        return versionInfo.FileVersion;
    }
    else
    {
        throw new ArgumentException("Unable to get version from path because the version is either null or empty: " + productVersionPath);
    }
}

public string DownloadLatestVersionOfChromeDriverGetURLToDownload(string version)
{
    if (String.IsNullOrEmpty(version))
    {
        throw new ArgumentException("Unable to get url because version is empty");
    }

    //URL's originates from here: https://chromedriver.chromium.org/downloads/version-selection
    string html = string.Empty;
    string urlToPathLocation = @"https://chromedriver.storage.googleapis.com/LATEST_RELEASE_" + String.Join(".", version.Split('.').Take(3));

    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(urlToPathLocation);
    request.AutomaticDecompression = DecompressionMethods.GZip;

    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    using (Stream stream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(stream))
    {
        html = reader.ReadToEnd();
    }

    if (String.IsNullOrEmpty(html))
    {
        throw new WebException("Unable to get version path from website");
    }

    return "https://chromedriver.storage.googleapis.com/" + html + "/chromedriver_win32.zip";
}

public void DownloadLatestVersionOfChromeDriverKillAllChromeDriverProcesses()
{
    //It's important to kill all processes before attempting to replace the chrome driver, because if you do not you may still have file locks left over
    var processes = Process.GetProcessesByName("chromedriver");
    foreach (var process in processes)
    {
        try
        {
            process.Kill();
        }
        catch
        {
            //We do our best here but if another user account is running the chrome driver we may not be able to kill it unless we run from a elevated user account + various other reasons we don't care about
        }
    }
}

public void DownloadLatestVersionOfChromeDriverDownloadNewVersionOfChrome(string urlToDownload)
{
    if (String.IsNullOrEmpty(urlToDownload))
    {
        throw new ArgumentException("Unable to get url because urlToDownload is empty");
    }

    //Downloaded files always come as a zip, we need to do a bit of switching around to get everything in the right place
    using (var client = new WebClient())
    {
        if (File.Exists(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\chromedriver.zip"))
        {
            File.Delete(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\chromedriver.zip");
        }

        client.DownloadFile(urlToDownload, "chromedriver.zip");

        if (File.Exists(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\chromedriver.zip") && File.Exists(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\chromedriver.exe"))
        {
            File.Delete(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\chromedriver.exe");
        }

        if (File.Exists(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\chromedriver.zip"))
        {
            System.IO.Compression.ZipFile.ExtractToDirectory(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\chromedriver.zip", System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,通常我会在应用程序的开头粘贴这个非常 hacky 的调用来调用此功能,并确保最新的 chromedriver 可用于我的应用程序:

//This is a very poor way of determining if I "need" to update the chromedriver,     
//however I've yet to figure out a better way of doing this...
try
{
    using (var chromeDriver = SetupChromeDriver())
    {
        chromeDriver.Navigate().GoToUrl("www.google.com");
        chromeDriver.Quit();
    }
}
catch
{
    DownloadLatestVersionOfChromeDriver();
}
Run Code Online (Sandbox Code Playgroud)

我确信这可以得到显着改善,但到目前为止它对我有用。

注:交叉发布在这里


Asy*_*nok 7

我认为您目前的方法不是可行的方法。新版本的浏览器发布时对 Selenium(或任何其他驱动程序)零考虑。一旦发布了新的浏览器更新,很可能没有现有的驱动程序适用于该版本。Selenium 团队通常需要几天时间才能发布更新的驱动程序以匹配最新版本的浏览器。

并且由于您会自动更新浏览器,因此您可能会自动破坏 Selenium 测试,直到发布新的驱动程序版本,或者直到您降级浏览器。

现在,您可能没问题,并且可以禁用浏览器的测试,直到最新的 Selenium 驱动程序与最新的浏览器版本兼容。如果是这种情况,那么这里有一些解决方案:

1)如果您使用 C#,请将您的驱动程序作为 Nuget 包或依赖项文件夹存储在测试解决方案中。然后,无论驱动程序在哪里运行,都有自动化引用该驱动程序。当您需要更新驱动程序时,您实际上只需要在一个地方更新它,并检查更改。所有客户端机器都将通过您的 CI 过程,下载最新的代码,其中包括新的驱动程序。

2)如果出于某种原因,您不希望项目中的驱动程序作为 Nuget 包或手动保存的依赖项,则让您的 CI 处理更新过程。将您的自动化代码指向位于当前正在运行的任何客户端机器上某个公共目录中的驱动程序 -> 下载依赖项后您的机器存储依赖项的位置。例如; 在 Windows 机器上通过控制台下载 selenium 文件会将它们放在 %APPDATA% "C:\Users\xxxxxx\AppData\Roaming\npm\node_modules" 中的某个位置。这就是您的测试解决方案的外观。

然后,在您的 CI 脚本中,在运行任何测试之前,下载最新的驱动程序。Windows 和 Linux/Unix 内核的语法几乎相同,即使不完全相同。这假设您已安装 npm。

npm install -g selenium
Run Code Online (Sandbox Code Playgroud)

如果你已经有了最新的,那么什么都不会发生。如果您不这样做,您的 CI 脚本将在运行测试之前下载最新的驱动程序。然后,您的测试解决方案将指向驱动程序在客户端上的存储位置,并且它将自动使用最新的驱动程序。