Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes/rpa fix chromedriverdownload #5690

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public SendMqttMessage(IMessageSenderClientFactory messageSenderClientFactory)
Order = 2,
UIHint = ActivityInputUIHints.MultiLine,
DefaultSyntax = SyntaxNames.Json,
SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid }
SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string Message { get; set; } = default!;

[ActivityOutput(Hint = "Received message")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public WebActivityWithSelector(IServiceProvider sp) : base(sp)
[ActivityInput(
UIHint = ActivityInputUIHints.Dropdown,
Hint = "The type of selector to be used to identity the element",
Options = new[] { SelectorTypes.ByName, SelectorTypes.ById, SelectorTypes.ByCss },
Options = new[] { SelectorTypes.ByName, SelectorTypes.ById, SelectorTypes.ByCss, SelectorTypes.ByXPath },
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? SelectorType { get; set; }
Expand Down Expand Up @@ -50,6 +50,7 @@ public WebActivityWithSelector(IServiceProvider sp) : base(sp)
{
case SelectorTypes.ById: { output.AddRange(driver.FindElements(By.Id(SelectorValue))); break; }
case SelectorTypes.ByName: { output.AddRange(driver.FindElements(By.Name(SelectorValue))); break; }
case SelectorTypes.ByCss: { output.AddRange(driver.FindElements(By.CssSelector(SelectorValue))); break; }
case SelectorTypes.ByXPath: { output.AddRange(driver.FindElements(By.XPath(SelectorValue))); break; }
case SelectorTypes.ByLinkText: { output.AddRange(driver.FindElements(By.LinkText(SelectorValue))); break; }
case SelectorTypes.Advanced:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Newtonsoft.Json;
namespace Elsa.Activities.Rpa.Web.DriverTypes.Chrome{
sfmskywalker marked this conversation as resolved.
Show resolved Hide resolved

public class Chrome
{
[JsonProperty("platform")]
public string Platform { get; set; }

[JsonProperty("url")]
public string Url { get; set; }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using System;
namespace Elsa.Activities.Rpa.Web.DriverTypes.Chrome{

public class ChromeForTesting
{
[JsonProperty("timestamp")]
public DateTime Timestamp { get; set; }

[JsonProperty("versions")]
public List<ChromeVersion> Versions { get; set; }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace Elsa.Activities.Rpa.Web.DriverTypes.Chrome{

public class ChromeVersion
{
[JsonProperty("version")]
public string VersionNumber { get; set; }

[JsonProperty("revision")]
public string Revision { get; set; }

[JsonProperty("downloads")]
public Downloads Downloads { get; set; }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Newtonsoft.Json;
namespace Elsa.Activities.Rpa.Web.DriverTypes.Chrome{

public class Chromedriver
{
[JsonProperty("platform")]
public string Platform { get; set; }

[JsonProperty("url")]
public string Url { get; set; }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Elsa.Activities.Rpa.Web.DriverTypes.Chrome{

public class Downloads
{
[JsonProperty("chrome")]
public List<Chrome> Chrome { get; set; }

[JsonProperty("chromedriver")]
public List<Chromedriver> Chromedriver { get; set; }
}

}
159 changes: 106 additions & 53 deletions src/activities/Elsa.Activities.Rpa.Web/Services/ChromeDriverInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Elsa.Activities.Rpa.Web.DriverTypes.Chrome;
using Newtonsoft.Json;

namespace Elsa.Activities.Rpa.Web.Services
{
Expand All @@ -17,60 +20,108 @@ public class ChromeDriverInstaller
{
BaseAddress = new Uri("https://chromedriver.storage.googleapis.com/")
};

public Task Install() => Install(null, false);
public Task Install(string chromeVersion) => Install(chromeVersion, false);
public Task Install(bool forceDownload) => Install(null, forceDownload);

public async Task Install(string? chromeVersion, bool forceDownload)
private async Task Install(string? chromeVersion, bool forceDownload)
{
// 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 ??= await GetChromeVersion();

// Take the Chrome version number, remove the last part,
chromeVersion = chromeVersion[..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".
var chromeDriverVersionResponse = await HttpClient.GetAsync($"LATEST_RELEASE_{chromeVersion}");
if (!chromeDriverVersionResponse.IsSuccessStatusCode)

// Take the Chrome version number, remove the build number
var chromeVersionWithoutBuiltNumber = chromeVersion[..chromeVersion.LastIndexOf('.')];

var chromeDriverVersion = chromeVersion;

var chromeVersionForTesting = new ChromeVersion();

// After 115 chromedriver changed the way it was downloaded so we need to account this new functionality https://developer.chrome.com/docs/chromedriver/downloads/version-selection
var useChromeForTestingDownloadApi = int.TryParse(chromeVersion.Split(".")[0], out var chromeMainVersionNumber) && chromeMainVersionNumber >= 115;

string zipName;
var driverName = "chromedriver";
string platformName;

var folderName = string.Empty;

if (useChromeForTestingDownloadApi)
{
if (chromeDriverVersionResponse.StatusCode == HttpStatusCode.NotFound)
var chromeToTestResponse = await HttpClient.GetAsync($"https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json");
var jsonResult = await chromeToTestResponse.Content.ReadAsStringAsync();
var chromeForTestingResult = JsonConvert.DeserializeObject<ChromeForTesting>(jsonResult);
chromeVersionForTesting = chromeForTestingResult?.Versions.SingleOrDefault(x => x.VersionNumber == chromeVersion)
?? chromeForTestingResult!.Versions.Last(x => x.VersionNumber.Contains(chromeVersionWithoutBuiltNumber));

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
folderName = RuntimeInformation.ProcessArchitecture == Architecture.X64 ? "chromedriver-win64" : "chromedriver-win32";
zipName = Path.ChangeExtension(folderName, "zip");
driverName = Path.ChangeExtension(driverName, "exe");
platformName = RuntimeInformation.ProcessArchitecture == Architecture.X64 ? "win64" : "win32";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
throw new Exception($"ChromeDriver version not found for Chrome version {chromeVersion}");
folderName = "chromedriver-linux64";
zipName = Path.ChangeExtension(folderName, "zip");
platformName = "linux64";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
folderName = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "chromedriver-mac-arm64" : "chromedriver-mac-x64";
zipName = Path.ChangeExtension(folderName, "zip");
platformName = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "mac-arm64" : "mac-x64";
}
else
{
throw new Exception($"ChromeDriver version request failed with status code: {chromeDriverVersionResponse.StatusCode}, reason phrase: {chromeDriverVersionResponse.ReasonPhrase}");
throw new PlatformNotSupportedException("Your operating system is not supported.");
}
}

var chromeDriverVersion = await chromeDriverVersionResponse.Content.ReadAsStringAsync();

string zipName;
string driverName;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
zipName = "chromedriver_win32.zip";
driverName = "chromedriver.exe";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
zipName = "chromedriver_linux64.zip";
driverName = "chromedriver";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
zipName = "chromedriver_mac64.zip";
driverName = "chromedriver";
}
else
{
throw new PlatformNotSupportedException("Your operating system is not supported.");
// 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".
var chromeDriverVersionResponse = await HttpClient.GetAsync($"LATEST_RELEASE_{chromeVersion}");
if (!chromeDriverVersionResponse.IsSuccessStatusCode)
{
if (chromeDriverVersionResponse.StatusCode == HttpStatusCode.NotFound)
{
throw new Exception($"ChromeDriver version not found for Chrome version {chromeVersion}");
}

throw new Exception($"ChromeDriver version request failed with status code: {chromeDriverVersionResponse.StatusCode}, reason phrase: {chromeDriverVersionResponse.ReasonPhrase}");
}

chromeDriverVersion = await chromeDriverVersionResponse.Content.ReadAsStringAsync();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
zipName = "chromedriver_win32.zip";
driverName = "chromedriver.exe";
platformName = "win32";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
zipName = "chromedriver_linux64.zip";
driverName = "chromedriver";
platformName = "linux64";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
zipName = "chromedriver_mac64.zip";
driverName = "chromedriver";
platformName = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "mac-arm64" : "mac-x64";
}
else
{
throw new PlatformNotSupportedException("Your operating system is not supported.");
}
}

string targetPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var targetPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
targetPath = Path.Combine(targetPath, driverName);
if (!forceDownload && File.Exists(targetPath))
{
Expand All @@ -85,44 +136,46 @@ public async Task Install(string? chromeVersion, bool forceDownload)
RedirectStandardError = true,
}
)!;
string existingChromeDriverVersion = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
var existingChromeDriverVersion = await process.StandardOutput.ReadToEndAsync();
var error = await process.StandardError.ReadToEndAsync();
process.WaitForExit();
process.Kill();

// expected output is something like "ChromeDriver 88.0.4324.96 (68dba2d8a0b149a1d3afac56fa74648032bcf46b-refs/branch-heads/4324@{#1784})"
// the following line will extract the version number and leave the rest
existingChromeDriverVersion = existingChromeDriverVersion.Split(" ")[1];
if (chromeDriverVersion == existingChromeDriverVersion)
{
return;
}

if (!string.IsNullOrEmpty(error))
{
throw new Exception($"Failed to execute {driverName} --version");
}
}

// 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/".
var driverZipResponse = await HttpClient.GetAsync($"{chromeDriverVersion}/{zipName}");
var driverZipResponse = useChromeForTestingDownloadApi
? await HttpClient.GetAsync(chromeVersionForTesting.Downloads.Chromedriver.Single(x => x.Platform == platformName).Url)
: await HttpClient.GetAsync($"{chromeDriverVersion}/{zipName}");
if (!driverZipResponse.IsSuccessStatusCode)
{
throw new Exception($"ChromeDriver download request failed with status code: {driverZipResponse.StatusCode}, reason phrase: {driverZipResponse.ReasonPhrase}");
}

// this reads the zipfile as a stream, opens the archive,
// and extracts the chromedriver executable to the targetPath without saving any intermediate files to disk
using (var zipFileStream = await driverZipResponse.Content.ReadAsStreamAsync())
using (var zipArchive = new ZipArchive(zipFileStream, ZipArchiveMode.Read))
using (var chromeDriverWriter = new FileStream(targetPath, FileMode.Create))
{
var entry = zipArchive.GetEntry(driverName)!;
var entry = zipArchive.GetEntry($"{folderName}/{driverName}")!;
using Stream chromeDriverStream = entry.Open();
await chromeDriverStream.CopyToAsync(chromeDriverWriter);
}

// on Linux/macOS, you need to add the executable permission (+x) to allow the execution of the chromedriver
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Expand All @@ -140,14 +193,14 @@ public async Task Install(string? chromeVersion, bool forceDownload)
string error = await process.StandardError.ReadToEndAsync();
process.WaitForExit();
process.Kill();

if (!string.IsNullOrEmpty(error))
{
throw new Exception("Failed to make chromedriver executable");
}
}
}

public async Task<string> GetChromeVersion()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Expand All @@ -157,7 +210,7 @@ public async Task<string> GetChromeVersion()
{
throw new Exception("Google Chrome not found in registry");
}

var fileVersionInfo = FileVersionInfo.GetVersionInfo(chromePath);
return fileVersionInfo.FileVersion;
}
Expand All @@ -180,12 +233,12 @@ public async Task<string> GetChromeVersion()
string error = await process.StandardError.ReadToEndAsync();
process.WaitForExit();
process.Kill();

if (!string.IsNullOrEmpty(error))
{
throw new Exception(error);
}

return output;
}
catch (Exception ex)
Expand All @@ -212,12 +265,12 @@ public async Task<string> GetChromeVersion()
string error = await process.StandardError.ReadToEndAsync();
process.WaitForExit();
process.Kill();

if (!string.IsNullOrEmpty(error))
{
throw new Exception(error);
}

output = output.Replace("Google Chrome ", "");
return output;
}
Expand Down
Loading