Skip to content

Commit

Permalink
Merge pull request #308 from Lombiq/issue/OSOE-668
Browse files Browse the repository at this point in the history
OSOE-668: "Creating the web driver failed" exception recently on GitHub-hosted Linux runners for Edge in Lombiq.UITestingToolbox
  • Loading branch information
Piedone authored Aug 14, 2023
2 parents e50c8e0 + 5fde6c5 commit 49bdd7c
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public Task VerifyBlogImage(Browser browser) =>
// is the different rendering of text on each platform, but it can occur between different Linux distributions too.
// Here: https://pandasauce.org/post/linux-fonts/ you can find a good summary about this from 2019, but still valid
// in 2022.
[Theory, Chrome]
[Theory, Chrome, Edge]
public Task VerifyNavbar(Browser browser) =>
ExecuteTestAfterSetupAsync(
context =>
Expand Down
25 changes: 18 additions & 7 deletions Lombiq.Tests.UI/Services/AtataFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using Lombiq.HelpfulLibraries.Common.Utilities;
using OpenQA.Selenium;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Xunit.Abstractions;

namespace Lombiq.Tests.UI.Services;
Expand All @@ -16,7 +18,16 @@ public class AtataConfiguration

public static class AtataFactory
{
[Obsolete($"Please use {nameof(StartAtataScopeAsync)} instead.")]
[SuppressMessage("Usage", "VSTHRD110:Observe result of async calls", Justification = "Backwards compatibility.")]
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Backwards compatibility.")]
public static AtataScope StartAtataScope(
ITestOutputHelper testOutputHelper,
Uri baseUri,
OrchardCoreUITestExecutorConfiguration configuration) =>
StartAtataScopeAsync(testOutputHelper, baseUri, configuration).GetAwaiter().GetResult();

public static async Task<AtataScope> StartAtataScopeAsync(
ITestOutputHelper testOutputHelper,
Uri baseUri,
OrchardCoreUITestExecutorConfiguration configuration)
Expand All @@ -33,7 +44,7 @@ public static AtataScope StartAtataScope(
var builder = AtataContext.Configure()
// The drivers are disposed when disposing AtataScope.
#pragma warning disable CA2000 // Dispose objects before losing scope
.UseDriver(CreateDriver(browserConfiguration, timeoutConfiguration, testOutputHelper))
.UseDriver(await CreateDriverAsync(browserConfiguration, timeoutConfiguration, testOutputHelper))
#pragma warning restore CA2000 // Dispose objects before losing scope
.UseBaseUrl(baseUri.ToString())
.UseCulture(browserConfiguration.AcceptLanguage.ToString())
Expand All @@ -56,12 +67,12 @@ public static void SetupShellCliCommandFactory() =>
.UseCmdForWindows()
.UseForOtherOS(new BashShellCliCommandFactory("-login"));

private static IWebDriver CreateDriver(
private static async Task<IWebDriver> CreateDriverAsync(
BrowserConfiguration browserConfiguration,
TimeoutConfiguration timeoutConfiguration,
ITestOutputHelper testOutputHelper)
{
IWebDriver From<T>(Func<BrowserConfiguration, TimeSpan, T> factory)
Task<T> FromAsync<T>(Func<BrowserConfiguration, TimeSpan, Task<T>> factory)
where T : IWebDriver =>
factory(browserConfiguration, timeoutConfiguration.PageLoadTimeout);

Expand All @@ -82,10 +93,10 @@ IWebDriver From<T>(Func<BrowserConfiguration, TimeSpan, T> factory)
{
return browserConfiguration.Browser switch
{
Browser.Chrome => From(WebDriverFactory.CreateChromeDriver),
Browser.Edge => From(WebDriverFactory.CreateEdgeDriver),
Browser.Firefox => From(WebDriverFactory.CreateFirefoxDriver),
Browser.InternetExplorer => From(WebDriverFactory.CreateInternetExplorerDriver),
Browser.Chrome => await FromAsync(WebDriverFactory.CreateChromeDriverAsync),
Browser.Edge => await FromAsync(WebDriverFactory.CreateEdgeDriverAsync),
Browser.Firefox => await FromAsync(WebDriverFactory.CreateFirefoxDriverAsync),
Browser.InternetExplorer => await FromAsync(WebDriverFactory.CreateInternetExplorerDriverAsync),
_ => throw new InvalidOperationException($"Unknown browser: {browserConfiguration.Browser}."),
};
}
Expand Down
2 changes: 1 addition & 1 deletion Lombiq.Tests.UI/Services/UITestExecutionSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ Task UITestingBeforeAppStartHandlerAsync(string contentRootPath, InstanceCommand
_configuration.Events.AfterPageChange += TakeScreenshotIfEnabledAsync;
}

var atataScope = AtataFactory.StartAtataScope(_testOutputHelper, uri, _configuration);
var atataScope = await AtataFactory.StartAtataScopeAsync(_testOutputHelper, uri, _configuration);

return new UITestContext(
contextId,
Expand Down
54 changes: 39 additions & 15 deletions Lombiq.Tests.UI/Services/WebDriverFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Atata.WebDriverSetup;
using Lombiq.HelpfulLibraries.Cli.Helpers;
using Lombiq.Tests.UI.Extensions;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
Expand All @@ -8,16 +9,19 @@
using OpenQA.Selenium.IE;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace Lombiq.Tests.UI.Services;

public static class WebDriverFactory
{
private static readonly object _setupLock = new();

public static ChromeDriver CreateChromeDriver(BrowserConfiguration configuration, TimeSpan pageLoadTimeout)
public static Task<ChromeDriver> CreateChromeDriverAsync(BrowserConfiguration configuration, TimeSpan pageLoadTimeout)
{
ChromeDriver CreateDriverInner(ChromeDriverService service)
Task<ChromeDriver> CreateDriverInnerAsync(ChromeDriverService service)
{
var chromeConfig = new ChromeConfiguration { Options = new ChromeOptions().SetCommonOptions() };

Expand Down Expand Up @@ -45,25 +49,36 @@ ChromeDriver CreateDriverInner(ChromeDriverService service)
// Helps with misconfigured hosts.
if (chromeConfig.Service.HostName == "localhost") chromeConfig.Service.HostName = "127.0.0.1";

return new ChromeDriver(chromeConfig.Service, chromeConfig.Options, pageLoadTimeout).SetCommonTimeouts(pageLoadTimeout);
return Task.FromResult(
new ChromeDriver(chromeConfig.Service, chromeConfig.Options, pageLoadTimeout)
.SetCommonTimeouts(pageLoadTimeout));
}

var chromeWebDriverPath = Environment.GetEnvironmentVariable("CHROMEWEBDRIVER"); // #spell-check-ignore-line
if (chromeWebDriverPath is { } driverPath && Directory.Exists(driverPath))
{
return CreateDriverInner(ChromeDriverService.CreateDefaultService(driverPath));
return CreateDriverInnerAsync(ChromeDriverService.CreateDefaultService(driverPath));
}

return CreateDriver(BrowserNames.Chrome, () => CreateDriverInner(service: null));
return CreateDriverAsync(BrowserNames.Chrome, () => CreateDriverInnerAsync(service: null));
}

public static EdgeDriver CreateEdgeDriver(BrowserConfiguration configuration, TimeSpan pageLoadTimeout) =>
CreateDriver(BrowserNames.Edge, () =>
public static Task<EdgeDriver> CreateEdgeDriverAsync(BrowserConfiguration configuration, TimeSpan pageLoadTimeout) =>
CreateDriverAsync(BrowserNames.Edge, async () =>
{
var options = new EdgeOptions().SetCommonOptions();

options.SetCommonChromiumOptions(configuration);

// While the Edge driver easily locates Edge on Windows, it struggles on Linux, where the different release
// channels have different executable names. This setting looks up the "microsoft-edge-stable" command and
// sets the full path as the browser's binary location.
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
(await CliWrapHelper.WhichAsync("microsoft-edge-stable"))?.FirstOrDefault() is { } binaryLocation)
{
options.BinaryLocation = binaryLocation.FullName;
}

configuration.BrowserOptionsConfigurator?.Invoke(options);

var service = EdgeDriverService.CreateDefaultService();
Expand All @@ -72,7 +87,7 @@ public static EdgeDriver CreateEdgeDriver(BrowserConfiguration configuration, Ti
return new EdgeDriver(service, options).SetCommonTimeouts(pageLoadTimeout);
});

public static FirefoxDriver CreateFirefoxDriver(BrowserConfiguration configuration, TimeSpan pageLoadTimeout)
public static Task<FirefoxDriver> CreateFirefoxDriverAsync(BrowserConfiguration configuration, TimeSpan pageLoadTimeout)
{
var options = new FirefoxOptions().SetCommonOptions();

Expand All @@ -89,19 +104,21 @@ public static FirefoxDriver CreateFirefoxDriver(BrowserConfiguration configurati

configuration.BrowserOptionsConfigurator?.Invoke(options);

return CreateDriver(BrowserNames.Firefox, () => new FirefoxDriver(options).SetCommonTimeouts(pageLoadTimeout));
return CreateDriverAsync(
BrowserNames.Firefox,
() => Task.FromResult(new FirefoxDriver(options).SetCommonTimeouts(pageLoadTimeout)));
}

public static InternetExplorerDriver CreateInternetExplorerDriver(BrowserConfiguration configuration, TimeSpan pageLoadTimeout) =>
CreateDriver(BrowserNames.InternetExplorer, () =>
public static Task<InternetExplorerDriver> CreateInternetExplorerDriverAsync(BrowserConfiguration configuration, TimeSpan pageLoadTimeout) =>
CreateDriverAsync(BrowserNames.InternetExplorer, () =>
{
var options = new InternetExplorerOptions().SetCommonOptions();

// IE doesn't support this.
options.AcceptInsecureCertificates = false;
configuration.BrowserOptionsConfigurator?.Invoke(options);

return new InternetExplorerDriver(options).SetCommonTimeouts(pageLoadTimeout);
return Task.FromResult(new InternetExplorerDriver(options).SetCommonTimeouts(pageLoadTimeout));
});

private static TDriverOptions SetCommonOptions<TDriverOptions>(this TDriverOptions driverOptions)
Expand Down Expand Up @@ -160,13 +177,13 @@ private static TDriver SetCommonTimeouts<TDriver>(this TDriver driver, TimeSpan
return driver;
}

private static TDriver CreateDriver<TDriver>(string browserName, Func<TDriver> driverFactory)
private static async Task<TDriver> CreateDriverAsync<TDriver>(string browserName, Func<Task<TDriver>> driverFactory)
where TDriver : IWebDriver
{
try
{
lock (_setupLock) DriverSetup.AutoSetUp(browserName);
return driverFactory();
AutoSetup(browserName);
return await driverFactory();
}
catch (Exception ex)
{
Expand All @@ -177,6 +194,13 @@ private static TDriver CreateDriver<TDriver>(string browserName, Func<TDriver> d
}
}

// We don't use the async version of auto setup because it doesn't do any locking. In fact it's just the sync method
// passed to Task.Run() so it wouldn't benefit us anyway.
private static void AutoSetup(string browserName)
{
lock (_setupLock) DriverSetup.AutoSetUp(browserName);
}

private sealed class ChromeConfiguration
{
public ChromeOptions Options { get; init; }
Expand Down

0 comments on commit 49bdd7c

Please sign in to comment.