Skip to content

Commit b60b3fc

Browse files
authored
Added support for chrome for testing apis (#254)
1 parent d335a3b commit b60b3fc

File tree

10 files changed

+266
-27
lines changed

10 files changed

+266
-27
lines changed

WebDriverManager.Tests/ChromeConfigTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public void VersionTest()
1616
}
1717

1818
[Fact]
19-
public void DriverDownloadTest()
19+
public void DriverDownloadLatestTest()
2020
{
2121
new DriverManager().SetUpDriver(new ChromeConfig());
2222
Assert.NotEmpty(WebDriverFinder.FindFile(GetBinaryName()));
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Net.Http;
3+
using System.Text.Json;
4+
using System.Threading.Tasks;
5+
using WebDriverManager.Models.Chrome;
6+
7+
namespace WebDriverManager.Clients
8+
{
9+
public static class ChromeForTestingClient
10+
{
11+
private static readonly string BaseUrl = "https://googlechromelabs.github.io/chrome-for-testing/";
12+
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions
13+
{
14+
PropertyNameCaseInsensitive = true
15+
};
16+
17+
private static readonly HttpClient _httpClient;
18+
19+
static ChromeForTestingClient()
20+
{
21+
_httpClient = new HttpClient
22+
{
23+
BaseAddress = new Uri(BaseUrl)
24+
};
25+
}
26+
27+
public static ChromeVersions GetKnownGoodVersionsWithDownloads()
28+
{
29+
return GetResultFromHttpTask<ChromeVersions>(
30+
_httpClient.GetAsync("known-good-versions-with-downloads.json")
31+
);
32+
}
33+
34+
public static ChromeVersions GetLastKnownGoodVersions()
35+
{
36+
return GetResultFromHttpTask<ChromeVersions>(
37+
_httpClient.GetAsync("last-known-good-versions-with-downloads.json")
38+
);
39+
}
40+
41+
/// <summary>
42+
/// Get a HTTP result without causing any deadlocks
43+
/// <para>See: https://learn.microsoft.com/en-us/archive/blogs/jpsanders/asp-net-do-not-use-task-result-in-main-context</para>
44+
/// </summary>
45+
/// <typeparam name="TResult">The type of result to convert the HTTP response to</typeparam>
46+
/// <param name="taskToRun">The <see cref="HttpResponseMessage"/> task to run</param>
47+
private static TResult GetResultFromHttpTask<TResult>(Task<HttpResponseMessage> taskToRun)
48+
where TResult : class
49+
{
50+
var httpTask = Task.Run(() => taskToRun);
51+
httpTask.Wait();
52+
53+
var readStringTask = Task.Run(() => httpTask.Result.Content.ReadAsStringAsync());
54+
readStringTask.Wait();
55+
56+
return JsonSerializer.Deserialize<TResult>(readStringTask.Result, JsonSerializerOptions);
57+
}
58+
}
59+
}

WebDriverManager/DriverConfigs/Impl/ChromeConfig.cs

Lines changed: 137 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
using System;
22
using System.IO;
3+
using System.Linq;
34
using System.Net;
45
using System.Runtime.InteropServices;
6+
using WebDriverManager.Clients;
57
using WebDriverManager.Helpers;
8+
using WebDriverManager.Models.Chrome;
69

710
namespace WebDriverManager.DriverConfigs.Impl
811
{
912
public class ChromeConfig : IDriverConfig
1013
{
1114
private const string BaseVersionPatternUrl = "https://chromedriver.storage.googleapis.com/<version>/";
12-
private const string LatestReleaseVersionUrl = "https://chromedriver.storage.googleapis.com/LATEST_RELEASE";
15+
private const string ExactReleaseVersionPatternUrl = "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_<version>";
1316

14-
private const string ExactReleaseVersionPatternUrl =
15-
"https://chromedriver.storage.googleapis.com/LATEST_RELEASE_<version>";
17+
/// <summary>
18+
/// The minimum version required to download chrome drivers from Chrome for Testing API's
19+
/// </summary>
20+
private static readonly Version MinChromeForTestingDriverVersion = new Version("115.0.5763.0");
21+
22+
/// <summary>
23+
/// The minimum version of chrome driver required to reference download URLs via the "arm64" extension
24+
/// </summary>
25+
private static readonly Version MinArm64ExtensionVersion = new Version("106.0.5249.61");
26+
27+
private ChromeVersionInfo _chromeVersionInfo;
28+
private string _chromeVersion;
1629

1730
public virtual string GetName()
1831
{
@@ -31,23 +44,14 @@ public virtual string GetUrl64()
3144

3245
private string GetUrl()
3346
{
34-
#if NETSTANDARD
35-
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
47+
// Handle newer versions of chrome driver only being available for download via the Chrome for Testing API's
48+
// whilst retaining backwards compatibility for older versions of chrome/chrome driver.
49+
if (_chromeVersionInfo != null)
3650
{
37-
var architectureExtension =
38-
RuntimeInformation.ProcessArchitecture == System.Runtime.InteropServices.Architecture.Arm64
39-
? "_arm64"
40-
: "64";
41-
return $"{BaseVersionPatternUrl}chromedriver_mac{architectureExtension}.zip";
51+
return GetUrlFromChromeForTestingApi();
4252
}
4353

44-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
45-
{
46-
return $"{BaseVersionPatternUrl}chromedriver_linux64.zip";
47-
}
48-
#endif
49-
50-
return $"{BaseVersionPatternUrl}chromedriver_win32.zip";
54+
return GetUrlFromChromeStorage();
5155
}
5256

5357
public virtual string GetBinaryName()
@@ -63,10 +67,49 @@ public virtual string GetBinaryName()
6367

6468
public virtual string GetLatestVersion()
6569
{
66-
return GetLatestVersion(LatestReleaseVersionUrl);
70+
var chromeReleases = ChromeForTestingClient.GetLastKnownGoodVersions();
71+
var chromeStable = chromeReleases.Channels.Stable;
72+
73+
_chromeVersionInfo = new ChromeVersionInfo
74+
{
75+
Downloads = chromeStable.Downloads
76+
};
77+
78+
return chromeStable.Version;
6779
}
6880

69-
private static string GetLatestVersion(string url)
81+
public virtual string GetMatchingBrowserVersion()
82+
{
83+
var rawChromeBrowserVersion = GetRawBrowserVersion();
84+
if (string.IsNullOrEmpty(rawChromeBrowserVersion))
85+
{
86+
throw new Exception("Not able to get chrome version or not installed");
87+
}
88+
89+
var chromeVersion = VersionHelper.GetVersionWithoutRevision(rawChromeBrowserVersion);
90+
91+
// Handle downloading versions of the chrome webdriver less than what's supported by the Chrome for Testing known good versions API
92+
// See https://googlechromelabs.github.io/chrome-for-testing for more info
93+
var matchedVersion = new Version(rawChromeBrowserVersion);
94+
if (matchedVersion < MinChromeForTestingDriverVersion)
95+
{
96+
var url = ExactReleaseVersionPatternUrl.Replace("<version>", chromeVersion);
97+
_chromeVersion = GetVersionFromChromeStorage(url);
98+
}
99+
else
100+
{
101+
_chromeVersion = GetVersionFromChromeForTestingApi(chromeVersion).Version;
102+
}
103+
104+
return _chromeVersion;
105+
}
106+
107+
/// <summary>
108+
/// Retrieves a chrome driver version string from https://chromedriver.storage.googleapis.com
109+
/// </summary>
110+
/// <param name="url">The request URL</param>
111+
/// <returns>A chrome driver version string</returns>
112+
private static string GetVersionFromChromeStorage(string url)
70113
{
71114
var uri = new Uri(url);
72115
var webRequest = WebRequest.Create(uri);
@@ -84,17 +127,85 @@ private static string GetLatestVersion(string url)
84127
}
85128
}
86129

87-
public virtual string GetMatchingBrowserVersion()
130+
/// <summary>
131+
/// Retrieves a download URL for a chrome driver from the https://chromedriver.storage.googleapis.com API's
132+
/// </summary>
133+
/// <returns>A chrome driver download URL</returns>
134+
private string GetUrlFromChromeStorage()
88135
{
89-
var rawChromeBrowserVersion = GetRawBrowserVersion();
90-
if (string.IsNullOrEmpty(rawChromeBrowserVersion))
136+
#if NETSTANDARD
137+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
91138
{
92-
throw new Exception("Not able to get chrome version or not installed");
139+
// Handle older versions of chrome driver arm64 builds that are tagged with 64_m1 instead of arm64.
140+
// See: https://chromedriver.storage.googleapis.com/index.html?path=106.0.5249.21/
141+
var useM1Prefix = new Version(_chromeVersion) < MinArm64ExtensionVersion;
142+
var armArchitectureExtension = useM1Prefix
143+
? "64_m1"
144+
: "_arm64";
145+
146+
var architectureExtension = RuntimeInformation.ProcessArchitecture == System.Runtime.InteropServices.Architecture.Arm64
147+
? armArchitectureExtension
148+
: "64";
149+
150+
return $"{BaseVersionPatternUrl}chromedriver_mac{architectureExtension}.zip";
93151
}
94152

95-
var chromeBrowserVersion = VersionHelper.GetVersionWithoutRevision(rawChromeBrowserVersion);
96-
var url = ExactReleaseVersionPatternUrl.Replace("<version>", chromeBrowserVersion);
97-
return GetLatestVersion(url);
153+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
154+
{
155+
return $"{BaseVersionPatternUrl}chromedriver_linux64.zip";
156+
}
157+
#endif
158+
159+
return $"{BaseVersionPatternUrl}chromedriver_win32.zip";
160+
}
161+
162+
/// <summary>
163+
/// Retrieves a chrome driver version string from https://googlechromelabs.github.io/chrome-for-testing
164+
/// </summary>
165+
/// <param name="version">The desired version to download</param>
166+
/// <returns>Chrome driver version info (version number, revision number, download URLs)</returns>
167+
private ChromeVersionInfo GetVersionFromChromeForTestingApi(string noRevisionVersion)
168+
{
169+
var knownGoodVersions = ChromeForTestingClient.GetKnownGoodVersionsWithDownloads();
170+
171+
// Pull latest patch version
172+
_chromeVersionInfo = knownGoodVersions.Versions.LastOrDefault(
173+
cV => cV.Version.Contains(noRevisionVersion)
174+
);
175+
176+
return _chromeVersionInfo;
177+
}
178+
179+
/// <summary>
180+
/// Retrieves a chrome driver download URL from Chrome for Testing API's
181+
/// </summary>
182+
/// <returns>A chrome driver download URL</returns>
183+
private string GetUrlFromChromeForTestingApi()
184+
{
185+
var platform = "win32";
186+
187+
#if NETSTANDARD
188+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
189+
{
190+
platform = RuntimeInformation.ProcessArchitecture == System.Runtime.InteropServices.Architecture.Arm64
191+
? "mac-arm64"
192+
: "mac-x64";
193+
}
194+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
195+
{
196+
platform = "linux64";
197+
}
198+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
199+
{
200+
platform = RuntimeInformation.ProcessArchitecture == System.Runtime.InteropServices.Architecture.X64
201+
? "win64"
202+
: "win32";
203+
}
204+
#endif
205+
var result = _chromeVersionInfo.Downloads.ChromeDriver
206+
.FirstOrDefault(driver => driver.Platform == platform);
207+
208+
return result.Url;
98209
}
99210

100211
private string GetRawBrowserVersion()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Collections.Generic;
2+
3+
namespace WebDriverManager.Models.Chrome
4+
{
5+
public class ChromeDownload
6+
{
7+
public IEnumerable<ChromePlatformInfo> ChromeDriver { get; set; }
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace WebDriverManager.Models.Chrome
2+
{
3+
public class ChromePlatformInfo
4+
{
5+
public string Platform { get; set; }
6+
7+
public string Url { get; set; }
8+
}
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace WebDriverManager.Models.Chrome
2+
{
3+
public class ChromeReleaseChannels
4+
{
5+
public ChromeReleaseTrack Stable { get; set; }
6+
7+
public ChromeReleaseTrack Beta { get; set; }
8+
9+
public ChromeReleaseTrack Dev { get; set; }
10+
11+
public ChromeReleaseTrack Canary { get; set; }
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace WebDriverManager.Models.Chrome
2+
{
3+
public class ChromeReleaseTrack
4+
{
5+
public string Channel { get; set; }
6+
7+
public string Version { get; set; }
8+
9+
public string Revision { get; set; }
10+
11+
public ChromeDownload Downloads { get; set; }
12+
}
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace WebDriverManager.Models.Chrome
2+
{
3+
public class ChromeVersionInfo
4+
{
5+
public string Version { get; set; }
6+
7+
public string Revision { get; set; }
8+
9+
public ChromeDownload Downloads { get; set; }
10+
}
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Collections.Generic;
2+
3+
namespace WebDriverManager.Models.Chrome
4+
{
5+
public class ChromeVersions
6+
{
7+
public string Timestamp { get; set; }
8+
9+
public ChromeReleaseChannels Channels { get; set; }
10+
11+
public IEnumerable<ChromeVersionInfo> Versions { get; set; }
12+
}
13+
}

WebDriverManager/WebDriverManager.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<PackageReference Include="AngleSharp" Version="1.0.4" />
2222
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
2323
<PackageReference Include="SharpZipLib" Version="1.4.2" />
24+
<PackageReference Include="System.Text.Json" Version="7.0.3" />
2425
</ItemGroup>
2526

2627
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">

0 commit comments

Comments
 (0)