diff --git a/.gitignore b/.gitignore
index 5fbcebe..8af2b54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,6 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
-launchSettings.json
# Build results
[Dd]ebug[-\w]*/
diff --git a/.vsts-ci.yml b/.vsts-ci.yml
index 82df15b..737a302 100644
--- a/.vsts-ci.yml
+++ b/.vsts-ci.yml
@@ -47,17 +47,11 @@ jobs:
displayName: Use GitVersion
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 5.0.101'
- inputs:
- packageType: sdk
- version: 5.0.101
-
- - task: UseDotNet@2
- displayName: 'Use .NET Core SDK 6.0.401'
+ displayName: 'Use .NET SDK'
retryCountOnTaskFailure: 3
inputs:
packageType: sdk
- version: 6.0.401
+ version: 7.0.302
- task: MSBuild@1
inputs:
@@ -110,16 +104,11 @@ jobs:
clean: true
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 5.0.101'
- inputs:
- packageType: sdk
- version: 5.0.101
-
- - task: UseDotNet@2
- displayName: 'Use .NET Core SDK 6.0.401'
+ displayName: 'Use .NET SDK'
+ retryCountOnTaskFailure: 3
inputs:
packageType: sdk
- version: 6.0.401
+ version: 7.0.302
- bash: |
chmod +x build/wasm-uitest-run.sh
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 563cf0b..7006b81 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -19,6 +19,7 @@
true
+
diff --git a/src/Sample/Sample.UITests/Constants.cs b/src/Sample/Sample.UITests/Constants.cs
index 4d862ff..74f168e 100644
--- a/src/Sample/Sample.UITests/Constants.cs
+++ b/src/Sample/Sample.UITests/Constants.cs
@@ -4,6 +4,7 @@
using System.Text;
using System.Threading.Tasks;
using Uno.UITest.Helpers.Queries;
+using Uno.UITests.Helpers;
namespace Sample.UITests
{
@@ -15,5 +16,7 @@ public class Constants
public readonly static string iOSDeviceNameOrId = "iPad Pro (12.9-inch) (5th generation)";
public readonly static Platform CurrentPlatform = Platform.Browser;
+
+ public readonly static Browser WebAssemblyBrowser = Browser.Chrome;
}
}
diff --git a/src/Sample/Sample.UITests/DragCoordinates_Tests.cs b/src/Sample/Sample.UITests/DragCoordinates_Tests.cs
index d0e5c51..95e5339 100644
--- a/src/Sample/Sample.UITests/DragCoordinates_Tests.cs
+++ b/src/Sample/Sample.UITests/DragCoordinates_Tests.cs
@@ -16,6 +16,8 @@ public class DragCoordinates_Tests : TestBase
[Test]
public void DragBorder01()
{
+ App.Screenshot("home screen");
+
Query testSelector = q => q.Marked("DragCoordinates 01");
Query rootCanvas = q => q.Marked("rootCanvas");
diff --git a/src/Sample/Sample.UITests/Sample.UITests.csproj b/src/Sample/Sample.UITests/Sample.UITests.csproj
index bb4e95b..3c097c7 100644
--- a/src/Sample/Sample.UITests/Sample.UITests.csproj
+++ b/src/Sample/Sample.UITests/Sample.UITests.csproj
@@ -2,7 +2,6 @@
net47
- 7.3
diff --git a/src/Sample/Sample.UITests/TestBase.cs b/src/Sample/Sample.UITests/TestBase.cs
index d607f85..815d7e7 100644
--- a/src/Sample/Sample.UITests/TestBase.cs
+++ b/src/Sample/Sample.UITests/TestBase.cs
@@ -40,6 +40,7 @@ public static void InitializeTestEnvrionment()
AppInitializer.TestEnvironment.AndroidAppName = Constants.AndroidAppName;
AppInitializer.TestEnvironment.iOSDeviceNameOrId = Constants.iOSDeviceNameOrId;
AppInitializer.TestEnvironment.CurrentPlatform = Constants.CurrentPlatform;
+ AppInitializer.TestEnvironment.WebAssemblyBrowser = Constants.WebAssemblyBrowser;
#if DEBUG
AppInitializer.TestEnvironment.WebAssemblyHeadless = false;
diff --git a/src/Sample/Sample.Wasm/Properties/launchSettings.json b/src/Sample/Sample.Wasm/Properties/launchSettings.json
index d158dba..720f995 100644
--- a/src/Sample/Sample.Wasm/Properties/launchSettings.json
+++ b/src/Sample/Sample.Wasm/Properties/launchSettings.json
@@ -11,6 +11,7 @@
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -18,10 +19,12 @@
"Sample.Wasm": {
"commandName": "Project",
"launchBrowser": true,
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+ "launchUrl": "http://localhost:55932/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5000"
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Sample/Sample.Wasm/Sample.Wasm.csproj b/src/Sample/Sample.Wasm/Sample.Wasm.csproj
index 32abcc0..98bd654 100644
--- a/src/Sample/Sample.Wasm/Sample.Wasm.csproj
+++ b/src/Sample/Sample.Wasm/Sample.Wasm.csproj
@@ -1,43 +1,49 @@
-
- Exe
- net6.0
- true
- $(DefineConstants);__WASM__
- NU1701
- true
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Uno.UITest.Helpers/AppInitializer.cs b/src/Uno.UITest.Helpers/AppInitializer.cs
index f552ff4..31b93c3 100644
--- a/src/Uno.UITest.Helpers/AppInitializer.cs
+++ b/src/Uno.UITest.Helpers/AppInitializer.cs
@@ -179,29 +179,38 @@ private static IApp CreateBrowserApp(bool alreadyRunningApp)
{
var configurator = Uno.UITest.Selenium.ConfigureApp
.WebAssembly
- .Uri(new Uri(TestEnvironment.WebAssemblyDefaultUri));
-
+ .Uri(new Uri(TestEnvironment.WebAssemblyDefaultUri))
+ .UsingBrowser(TestEnvironment.WebAssemblyBrowser.ToString());
if(!string.IsNullOrEmpty(TestEnvironment.ChromeDriverPath))
{
- configurator = configurator.ChromeDriverLocation(
- Path.Combine(TestContext.CurrentContext.TestDirectory,
- TestEnvironment.ChromeDriverPath.Replace('\\', Path.DirectorySeparatorChar)));
+ var driverPath = Path.Combine(
+ TestContext.CurrentContext.TestDirectory,
+ TestEnvironment.ChromeDriverPath.Replace('\\', Path.DirectorySeparatorChar));
+ configurator = configurator.DriverPath(driverPath);
+ }
+ else if(!string.IsNullOrEmpty(TestEnvironment.SeleniumDriverPath))
+ {
+ var driverPath = Path.Combine(
+ TestContext.CurrentContext.TestDirectory,
+ TestEnvironment.SeleniumDriverPath.Replace('\\', Path.DirectorySeparatorChar));
+ configurator = configurator.DriverPath(driverPath);
}
- if(!TestEnvironment.WebAssemblyHeadless)
+ if(TestEnvironment.WebAssemblyHeadless)
{
configurator = configurator
- .Headless(false)
- .SeleniumArgument("--remote-debugging-port=9222");
+ .Headless(true);
}
else
{
configurator = configurator
- .Headless(true);
+ .Headless(false)
+ .SeleniumArgument("--remote-debugging-port=9222");
}
- _currentApp = configurator.ScreenShotsPath(TestContext.CurrentContext.TestDirectory)
+ _currentApp = configurator
+ .ScreenShotsPath(TestContext.CurrentContext.TestDirectory)
.StartApp();
return _currentApp;
diff --git a/src/Uno.UITest.Helpers/AppInitializerEnvironment.cs b/src/Uno.UITest.Helpers/AppInitializerEnvironment.cs
index 124389d..93885bd 100644
--- a/src/Uno.UITest.Helpers/AppInitializerEnvironment.cs
+++ b/src/Uno.UITest.Helpers/AppInitializerEnvironment.cs
@@ -45,9 +45,26 @@ internal AppInitializerEnvironment()
///
public string ChromeDriverPath { get; set; }
+ ///
+ /// Defines the location of selenium driver.
+ ///
+ ///
+ /// If not defined, the test engine will select the version based on
+ /// the currently installed browsers version.
+ ///
+ public string SeleniumDriverPath { get; set; }
+
///
/// Defines if the browser tests are running in chrome without a window.
///
public bool WebAssemblyHeadless { get; set; } = true;
+
+ ///
+ /// Defines the browser to use for the Web platform. Cf. remarks about compatibility
+ ///
+ ///
+ /// Note that all browser does not supports all options defined here. For instance Edge does support only the .
+ ///
+ public Browser WebAssemblyBrowser { get; set; } = Browser.Chrome;
}
}
diff --git a/src/Uno.UITest.Helpers/Browser.cs b/src/Uno.UITest.Helpers/Browser.cs
new file mode 100644
index 0000000..6e5856b
--- /dev/null
+++ b/src/Uno.UITest.Helpers/Browser.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Linq;
+
+namespace Uno.UITests.Helpers
+{
+ ///
+ /// Supported web browsers for UI tests
+ ///
+ public enum Browser
+ {
+ Chrome,
+
+ ///
+ /// The **CHROMIUM Based** Edge web browser
+ ///
+ Edge
+ }
+}
diff --git a/src/Uno.UITest.Puppeteer/ChromeAppConfigurator.cs b/src/Uno.UITest.Puppeteer/ChromeAppConfigurator.cs
deleted file mode 100644
index 9df7f52..0000000
--- a/src/Uno.UITest.Puppeteer/ChromeAppConfigurator.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Uno.UITest.Selenium
-{
- public class ChromeAppConfigurator
- {
- internal string ChromeDriverPath { get; private set; }
- internal Uri SiteUri { get; private set; }
- internal string InternalScreenShotsPath { get; private set; } = "";
- internal bool InternalHeadless { get; private set; } = true;
- internal int InternalWindowWidth { get; private set; } = 1024;
- internal int InternalWindowHeight { get; private set; } = 768;
- internal string InternalBrowserBinaryPath { get; private set; }
- internal List InternalSeleniumArgument = new List();
- internal bool InternalDetectDockerEnvironment = true;
-
- public ChromeAppConfigurator()
- {
- }
-
- public ChromeAppConfigurator Uri(Uri uri) { SiteUri = uri; return this; }
-
- public ChromeAppConfigurator ChromeDriverLocation(string chromeDriverPath) { ChromeDriverPath = chromeDriverPath; return this; }
-
- public ChromeAppConfigurator ScreenShotsPath(string path) { InternalScreenShotsPath = path; return this; }
-
- public ChromeAppConfigurator BrowserBinaryPath(string path) { InternalBrowserBinaryPath = path; return this; }
-
- ///
- /// This parameters allows to provide a set of additional parameters to be provided to the WebDriver.
- ///
- public ChromeAppConfigurator SeleniumArgument(string argument) { InternalSeleniumArgument.Add(argument); return this; }
-
-
- ///
- /// Enables the detection of the docker environment to configure the WebDriver accordingly. Enabled by default.
- ///
- public ChromeAppConfigurator DetectDockerEnvironment(bool enabled) { InternalDetectDockerEnvironment = enabled; return this; }
-
- ///
- /// Runs the browser as headless. Defaults to true.
- ///
- ///
- ///
- public ChromeAppConfigurator Headless(bool isHeadless) { InternalHeadless = isHeadless; return this; }
-
-
- ///
- /// Sets the window size. Defaults to 1024x768
- ///
- ///
- ///
- ///
- public ChromeAppConfigurator WindowSize(int width, int height)
- {
- InternalWindowWidth = width;
- InternalWindowHeight = height;
- return this;
- }
-
- public IApp StartApp() => new SeleniumApp(this);
- }
-}
diff --git a/src/Uno.UITest.Puppeteer/ConfigureApp.cs b/src/Uno.UITest.Puppeteer/ConfigureApp.cs
index 60a4e62..9d6f843 100644
--- a/src/Uno.UITest.Puppeteer/ConfigureApp.cs
+++ b/src/Uno.UITest.Puppeteer/ConfigureApp.cs
@@ -6,7 +6,6 @@ namespace Uno.UITest.Selenium
{
public static class ConfigureApp
{
- public static ChromeAppConfigurator WebAssembly =>
- new ChromeAppConfigurator();
+ public static SeleniumAppConfigurator WebAssembly => new SeleniumAppConfigurator();
}
}
diff --git a/src/Uno.UITest.Puppeteer/Models/ChromeDriverVersionModels.cs b/src/Uno.UITest.Puppeteer/Models/ChromeDriverVersionModels.cs
new file mode 100644
index 0000000..78aa034
--- /dev/null
+++ b/src/Uno.UITest.Puppeteer/Models/ChromeDriverVersionModels.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace Uno.UITest.Selenium.Models
+{
+
+ // Models generated by json2csharp.com from https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json
+ public class Chrome
+ {
+ [JsonPropertyName("platform")]
+ public string Platform { get; set; }
+
+ [JsonPropertyName("url")]
+ public string Url { get; set; }
+ }
+
+ public class Chromedriver
+ {
+ [JsonPropertyName("platform")]
+ public string Platform { get; set; }
+
+ [JsonPropertyName("url")]
+ public string Url { get; set; }
+ }
+
+ public class Downloads
+ {
+ [JsonPropertyName("chrome")]
+ public Chrome[] Chrome { get; set; }
+
+ [JsonPropertyName("chromedriver")]
+ public Chromedriver[] Chromedriver { get; set; }
+ }
+
+ public class Root
+ {
+ [JsonPropertyName("timestamp")]
+ public DateTime? Timestamp { get; set; }
+
+ [JsonPropertyName("versions")]
+ public ChromeVersion[] Versions { get; set; }
+ }
+
+ public class ChromeVersion
+ {
+ [JsonPropertyName("version")]
+ public string Version { get; set; }
+
+ [JsonPropertyName("revision")]
+ public string Revision { get; set; }
+
+ [JsonPropertyName("downloads")]
+ public Downloads Downloads { get; set; }
+ }
+
+}
diff --git a/src/Uno.UITest.Puppeteer/SeleniumApp.cs b/src/Uno.UITest.Puppeteer/SeleniumApp.cs
index 5d0221c..9412285 100644
--- a/src/Uno.UITest.Puppeteer/SeleniumApp.cs
+++ b/src/Uno.UITest.Puppeteer/SeleniumApp.cs
@@ -3,15 +3,12 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
-using System.IO.Compression;
using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Security.Policy;
using System.Threading;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
+using OpenQA.Selenium.Chromium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Remote;
using static System.Math;
@@ -20,69 +17,14 @@ namespace Uno.UITest.Selenium
{
public partial class SeleniumApp : IApp
{
- private const string UNO_UITEST_DRIVERPATH_CHROME = "UNO_UITEST_DRIVERPATH_CHROME";
-
-
- private readonly ChromeDriver _driver;
- private string _screenShotPath;
+ private readonly ChromiumDriver _driver;
+ private readonly string _screenShotPath;
private readonly TimeSpan DefaultRetry = TimeSpan.FromMilliseconds(500);
private readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(1);
-
- public SeleniumApp(ChromeAppConfigurator config)
+ public SeleniumApp(ChromiumDriver driver, string screenShotPath)
{
- var targetUri = GetEnvironmentVariable("UNO_UITEST_TARGETURI", config.SiteUri.OriginalString);
- var driverPath = GetEnvironmentVariable(UNO_UITEST_DRIVERPATH_CHROME, config.ChromeDriverPath);
- var screenShotPath = GetEnvironmentVariable("UNO_UITEST_SCREENSHOT_PATH", config.InternalScreenShotsPath);
- var chromeBinPath = GetEnvironmentVariable("UNO_UITEST_CHROME_BINARY_PATH", config.InternalBrowserBinaryPath);
-
- var options = new ChromeOptions();
-
- if(config.InternalHeadless)
- {
- options.AddArguments("--no-sandbox");
- options.AddArguments("--disable-dev-shm-usage");
- options.AddArgument("headless");
- }
-
- options.AddArgument($"window-size={config.InternalWindowWidth}x{config.InternalWindowHeight}");
-
- if(config.InternalDetectDockerEnvironment)
- {
- if(File.Exists("/.dockerenv"))
- {
- // When running under docker, ports bindings may not work properly
- // as the current local host may not be detected properly by the web driver
- // causing errors like this one:
- //
- // [SEVERE]: bind() returned an error, errno=99: Cannot assign requested address (99)
- //
- // When InternalDetectDockerEnvironment is set, tell the daemon to listen on
- // all available interfaces
- Console.WriteLine($"Container mode enabled, adding whitelisted-ips");
- options.AddArguments("--whitelisted-ips");
- }
- }
-
- foreach(var arg in config.InternalSeleniumArgument)
- {
- options.AddArguments(arg);
- }
-
- if(!string.IsNullOrEmpty(chromeBinPath))
- {
- options.BinaryLocation = chromeBinPath;
- }
-
- if(string.IsNullOrEmpty(driverPath))
- {
- driverPath = TryDownloadChromeDriver();
- }
-
- options.SetLoggingPreference(LogType.Browser, LogLevel.All);
-
- _driver = new ChromeDriver(driverPath, options);
- _driver.Url = targetUri;
+ _driver = driver;
_screenShotPath = screenShotPath;
}
@@ -92,145 +34,7 @@ IQueryable IApp.GetSystemLogs(DateTime? afterDate)
.Where(entry => afterDate == null || entry.Timestamp > afterDate)
.Select(entry => new SeleniumLogEntry(entry));
- private string TryDownloadChromeDriver()
- {
- if(Environment.OSVersion.Platform == PlatformID.Win32NT)
- {
- var chromePath = $@"{Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)}\Google\Chrome\Application\chrome.exe";
- // Chrome might be installed in C:\Program Files\Google...
- // If file doesn't exist, check there.
- if(!File.Exists(chromePath))
- {
- // Using environment variable here since EnvironMent.SpecialFolder.ProgramFiles resolves to the X86
- // variant depending on the executable architecture. The path variable always evaluates to the correct path though.
- chromePath = $@"{Environment.GetEnvironmentVariable("ProgramW6432")}\Google\Chrome\Application\chrome.exe";
- }
- chromePath = chromePath.Replace("\\", "\\\\");
-
- var process = new Process();
- process.StartInfo.FileName = "wmic.exe";
- process.StartInfo.Arguments = $@"datafile where name=""{chromePath}"" get Version /value";
- process.StartInfo.UseShellExecute = false;
- process.StartInfo.RedirectStandardOutput = true;
- process.StartInfo.RedirectStandardError = true;
- process.Start();
-
- var wincOutput = process.StandardOutput.ReadToEnd();
- var chromeRawVersion = wincOutput.Split('=').LastOrDefault()?.Trim();
-
- if(Version.TryParse(chromeRawVersion, out var chromeVersion))
- {
- var driverLocalPath = Path.Combine(Path.GetTempPath(), "Uno.UITests", "ChromeDriver", chromeVersion.ToString());
- Directory.CreateDirectory(driverLocalPath);
-
- var driverPath = Path.Combine(driverLocalPath, "chromedriver.exe");
-
- if(!File.Exists(driverPath))
- {
- // Chrome driver selection: http://chromedriver.chromium.org/downloads/version-selection
-
- var chromeDriverLatestVersionUri = $"https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{chromeVersion.Major}";
-
- Console.WriteLine($"Fetching Chrome driver version for Chrome [{chromeRawVersion}]");
-#if NET6_0_OR_GREATER
- var driverVersion = Task.Run(async () =>
- {
- using var client = new HttpClient();
- return await client.GetStringAsync(chromeDriverLatestVersionUri);
- }).Result;
-#else
- var driverVersion = new WebClient().DownloadString(chromeDriverLatestVersionUri);
-#endif
-
- var chromeDriverVersionUri = $"https://chromedriver.storage.googleapis.com/{driverVersion}/chromedriver_win32.zip";
-
- var tempZipFileName = Path.GetTempFileName();
-
- try
- {
- Console.WriteLine($"Downloading Chrome driver from [{chromeDriverVersionUri}]");
-#if NET6_0_OR_GREATER
- Task.Run(async () =>
- {
- using var client = new HttpClient();
- using var response = await client.GetAsync(chromeDriverVersionUri);
- if(!response.IsSuccessStatusCode)
- {
- return;
- }
-
- var fileInfo = new FileInfo(tempZipFileName);
- if(!fileInfo.Directory.Exists)
- {
- fileInfo.Directory.Create();
- }
- if(fileInfo.Exists)
- {
- fileInfo.Delete();
- }
-
- using var writer = fileInfo.OpenWrite();
- using var responseStream = await response.Content.ReadAsStreamAsync();
- await responseStream.CopyToAsync(writer);
- }).Wait();
-#else
- new WebClient().DownloadFile(chromeDriverVersionUri, tempZipFileName);
-#endif
-
- using(var zipFile = ZipFile.OpenRead(tempZipFileName))
- {
- zipFile.GetEntry("chromedriver.exe").ExtractToFile(driverPath, true);
- }
- }
- finally
- {
- if(File.Exists(tempZipFileName))
- {
- File.Delete(tempZipFileName);
- }
- }
- }
-
- return Path.GetDirectoryName(driverPath);
- }
- else
- {
- throw new NotSupportedException($"Unable to determine the chrome driver version. The used path was [{chromePath}], found [{chromeVersion}].");
- }
- }
- else
- {
- throw new NotSupportedException($"Unable to determine the chrome driver location. Use the {UNO_UITEST_DRIVERPATH_CHROME} environment variable.");
- }
- }
-
- private string GetEnvironmentVariable(string variableName, string defaultValue)
- {
- var value = Environment.GetEnvironmentVariable(variableName);
-
- var hasValue = !string.IsNullOrWhiteSpace(value);
-
- if(hasValue)
- {
- Console.WriteLine($"Overriding value with {variableName} = {value}");
- }
-
- return hasValue ? value : defaultValue;
- }
-
- private bool GetEnvironmentVariable(string variableName, bool defaultValue)
- {
- var value = Environment.GetEnvironmentVariable(variableName);
-
- var hasValue = bool.TryParse(value, out var varValue);
-
- if(hasValue)
- {
- Console.WriteLine($"Overriding value with {variableName} = {value}");
- }
-
- return hasValue ? varValue : defaultValue;
- }
+ public static SeleniumDriverManager SelectedBrowser { get; set; }
void PerformActions(Action action)
{
@@ -490,7 +294,6 @@ void IApp.Tap(Func query)
void IApp.TapCoordinates(float x, float y)
{
-
PerformActions(a => a
.MoveToElement(_driver.FindElement(By.TagName("body")), 0, 0)
.MoveByOffset((int)x, (int)y)
@@ -680,6 +483,5 @@ private IWebElement GetSingleElement(Func query)
throw new InvalidOperationException($"Invalid query results");
}
-
}
}
diff --git a/src/Uno.UITest.Puppeteer/SeleniumAppConfigurator.cs b/src/Uno.UITest.Puppeteer/SeleniumAppConfigurator.cs
new file mode 100644
index 0000000..b213cf9
--- /dev/null
+++ b/src/Uno.UITest.Puppeteer/SeleniumAppConfigurator.cs
@@ -0,0 +1,237 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using OpenQA.Selenium.Chrome;
+using OpenQA.Selenium.Chromium;
+using OpenQA.Selenium.Edge;
+using OpenQA.Selenium.Remote;
+
+namespace Uno.UITest.Selenium
+{
+ public class SeleniumAppConfigurator
+ {
+ internal const string UNO_UITEST_DRIVER_PATH = "UNO_UITEST_DRIVER_PATH";
+
+ private string _browser;
+ private Uri SiteUri { get; set; }
+ private string InternalScreenShotsPath { get; set; } = "";
+ private string InternalDriverPath { get; set; }
+ private string InternalBrowserPath { get; set; }
+ private bool InternalHeadless { get; set; } = true;
+ private int InternalWindowWidth { get; set; } = 1024;
+ private int InternalWindowHeight { get; set; } = 768;
+ private List InternalSeleniumArgument = new List();
+ private bool InternalDetectDockerEnvironment = true;
+
+ #region Fluent declaration
+ public SeleniumAppConfigurator UsingBrowser(string browser)
+ {
+ _browser = browser;
+ return this;
+ }
+
+ public SeleniumAppConfigurator Uri(Uri uri)
+ {
+ SiteUri = uri;
+ return this;
+ }
+
+ public SeleniumAppConfigurator ScreenShotsPath(string path)
+ {
+ InternalScreenShotsPath = path;
+ return this;
+ }
+
+ public SeleniumAppConfigurator BrowserBinaryPath(string path)
+ {
+ InternalBrowserPath = path;
+ return this;
+ }
+
+ public SeleniumAppConfigurator BrowserPath(string path)
+ {
+ InternalBrowserPath = path;
+ return this;
+ }
+
+ public SeleniumAppConfigurator DriverPath(string driverPath)
+ {
+ InternalDriverPath = driverPath;
+ return this;
+ }
+
+ ///
+ /// This parameters allows to provide a set of additional parameters to be provided to the WebDriver.
+ ///
+ public SeleniumAppConfigurator SeleniumArgument(string argument)
+ {
+ InternalSeleniumArgument.Add(argument);
+ return this;
+ }
+
+
+ ///
+ /// Enables the detection of the docker environment to configure the WebDriver accordingly. Enabled by default.
+ ///
+ public SeleniumAppConfigurator DetectDockerEnvironment(bool enabled)
+ {
+ InternalDetectDockerEnvironment = enabled;
+ return this;
+ }
+
+ ///
+ /// Runs the browser as headless. Defaults to true.
+ ///
+ ///
+ ///
+ public SeleniumAppConfigurator Headless(bool isHeadless)
+ {
+ InternalHeadless = isHeadless;
+ return this;
+ }
+
+ ///
+ /// Sets the window size. Defaults to 1024x768
+ ///
+ ///
+ ///
+ ///
+ public SeleniumAppConfigurator WindowSize(int width, int height)
+ {
+ InternalWindowWidth = width;
+ InternalWindowHeight = height;
+ return this;
+ }
+ #endregion
+
+ public IApp StartApp()
+ {
+ var targetUri = GetEnvironmentVariable("UNO_UITEST_TARGETURI", SiteUri.OriginalString);
+ var screenShotPath = GetEnvironmentVariable("UNO_UITEST_SCREENSHOT_PATH", InternalScreenShotsPath);
+ var browser = GetEnvironmentVariable("UNO_UITEST_BROWSER", _browser);
+ var browserPath = GetEnvironmentVariable("UNO_UITEST_BROWSER_PATH", InternalBrowserPath);
+ var driverPath = GetEnvironmentVariable(UNO_UITEST_DRIVER_PATH, InternalDriverPath);
+
+ ChromiumDriver driver;
+ switch(browser?.ToUpperInvariant())
+ {
+ case "EDGE":
+ driver = GetEdgeDriver(browserPath, driverPath);
+ break;
+
+ case "CHROME":
+ driver = GetChromeDriver(browserPath, driverPath);
+ break;
+
+ default:
+ if(browserPath?.Contains("edge") ?? driverPath?.Contains("edge") ?? false)
+ {
+ driver = GetEdgeDriver(browserPath, driverPath);
+ }
+ else
+ {
+ driver = GetChromeDriver(browserPath, driverPath);
+ }
+ break;
+ }
+ driver.Url = targetUri;
+
+ return new SeleniumApp(driver, screenShotPath);
+ }
+
+ protected ChromiumDriver GetChromeDriver(string browserPath = null, string driverPath = null)
+ {
+ // For backward compatibility, we give priority to the "CHROME" specific env. variables
+ driverPath = GetEnvironmentVariable("UNO_UITEST_DRIVERPATH_CHROME", driverPath);
+ browserPath = GetEnvironmentVariable("UNO_UITEST_CHROME_BINARY_PATH", browserPath);
+
+ var options = new ChromeOptions();
+ ApplyOptions(options, browserPath);
+
+ var driver = string.IsNullOrEmpty(driverPath)
+ ? SeleniumDriverManager.Chrome.FromChromePath(browserPath, options)
+ : SeleniumDriverManager.Chrome.FromDriverPath(driverPath, options);
+
+ return driver;
+ }
+
+ protected ChromiumDriver GetEdgeDriver(string browserPath = null, string driverPath = null)
+ {
+ var options = new EdgeOptions();
+ ApplyOptions(options, browserPath);
+
+ var driver = string.IsNullOrEmpty(driverPath)
+ ? SeleniumDriverManager.Edge.FromEdgePath(browserPath, options)
+ : SeleniumDriverManager.Edge.FromDriverPath(driverPath, options);
+
+ return driver;
+ }
+
+ private void ApplyOptions(ChromiumOptions options, string browserPath)
+ {
+ if(InternalHeadless)
+ {
+ options.AddArguments("--no-sandbox");
+ options.AddArgument("headless");
+ }
+
+ options.AddArgument($"window-size={InternalWindowWidth}x{InternalWindowHeight}");
+
+ if(InternalDetectDockerEnvironment)
+ {
+ if(File.Exists("/.dockerenv"))
+ {
+ // When running under docker, ports bindings may not work properly
+ // as the current local host may not be detected properly by the web driver
+ // causing errors like this one:
+ //
+ // [SEVERE]: bind() returned an error, errno=99: Cannot assign requested address (99)
+ //
+ // When InternalDetectDockerEnvironment is set, tell the daemon to listen on
+ // all available interfaces
+ Console.WriteLine($"Container mode enabled, adding whitelisted-ips");
+ options.AddArguments("--whitelisted-ips");
+ }
+ }
+
+ foreach(var arg in InternalSeleniumArgument)
+ {
+ options.AddArguments(arg);
+ }
+
+ if(!string.IsNullOrEmpty(browserPath))
+ {
+ options.BinaryLocation = browserPath;
+ }
+ }
+
+ #region Helpers
+ protected string GetEnvironmentVariable(string variableName, string defaultValue)
+ {
+ var value = Environment.GetEnvironmentVariable(variableName);
+ var hasValue = !string.IsNullOrWhiteSpace(value);
+
+ if(hasValue)
+ {
+ Console.WriteLine($"Overriding value with {variableName} = {value}");
+ }
+
+ return hasValue ? value : defaultValue;
+ }
+
+ protected bool GetEnvironmentVariable(string variableName, bool defaultValue)
+ {
+ var value = Environment.GetEnvironmentVariable(variableName);
+
+ var hasValue = bool.TryParse(value, out var varValue);
+
+ if(hasValue)
+ {
+ Console.WriteLine($"Overriding value with {variableName} = {value}");
+ }
+
+ return hasValue ? varValue : defaultValue;
+ }
+ #endregion
+ }
+}
diff --git a/src/Uno.UITest.Puppeteer/SeleniumDriverManager.cs b/src/Uno.UITest.Puppeteer/SeleniumDriverManager.cs
new file mode 100644
index 0000000..f583ce8
--- /dev/null
+++ b/src/Uno.UITest.Puppeteer/SeleniumDriverManager.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Net;
+using System.Text.Json;
+using OpenQA.Selenium.Chrome;
+using OpenQA.Selenium.Edge;
+
+namespace Uno.UITest.Selenium
+{
+ public class SeleniumDriverManager
+ {
+ public static class Chrome
+ {
+ // Chrome driver selection: http://chromedriver.chromium.org/downloads/version-selection
+
+ public static ChromeDriver FromChromePath(string chromePath, ChromeOptions options)
+ => new ChromeDriver(
+ new SeleniumDriverManager("chromedriver").GetOrDownloadLatestDriverForBin(
+ ChromeFilePath(),
+ GetDriverLatestVersion,
+ GetDriverUri).FullName,
+ options);
+
+ private static string ChromeFilePath()
+ {
+ var chromePath = $@"{Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)}\Google\Chrome\Application\chrome.exe";
+ // Chrome might be installed in C:\Program Files\Google...
+ // If file doesn't exist, check there.
+ if(!File.Exists(chromePath))
+ {
+ // Using environment variable here since EnvironMent.SpecialFolder.ProgramFiles resolves to the X86
+ // variant depending on the executable architecture. The path variable always evaluates to the correct path though.
+ chromePath = $@"{Environment.GetEnvironmentVariable("ProgramW6432")}\Google\Chrome\Application\chrome.exe";
+ }
+ return chromePath;
+ }
+
+ public static ChromeDriver FromDriverPath(string driverPath, ChromeOptions options)
+ => new ChromeDriver(driverPath, options);
+
+ private static Uri GetDriverLatestVersion(Version browserVersion) =>
+ browserVersion.Major <= 114 ?
+ new Uri($"https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{browserVersion.Major}") :
+ new Uri("https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json");
+
+ private static Uri GetDriverUri(Version browserVersion, string driverVersion)
+ {
+ if(browserVersion.Major <= 114)
+ {
+ return new Uri($"https://chromedriver.storage.googleapis.com/{driverVersion}/chromedriver_win32.zip");
+ }
+ else
+ {
+ var driverInfo = JsonSerializer.Deserialize(driverVersion);
+ return new Uri((from v in driverInfo.Versions
+ let ver = Version.TryParse(v.Version, out var parsedVersion) ? parsedVersion : default
+ where ver.Major == browserVersion.Major &&
+ ver.Minor == browserVersion.Minor &&
+ (v.Downloads?.Chromedriver?.Any() ?? false)
+ orderby ver descending
+ from platform in v.Downloads.Chromedriver
+ where platform.Platform == "win32"
+ select platform.Url).First());
+ }
+ }
+
+ }
+
+ public static class Edge
+ {
+ // Edge driver selection https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/#downloads
+ // Edge driver documentation https://docs.microsoft.com/en-us/microsoft-edge/webdriver-chromium
+
+ public static EdgeDriver FromEdgePath(string edgePath, EdgeOptions options)
+ {
+ edgePath = edgePath ?? $@"{Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)}\Microsoft\Edge\Application\msedge.exe";
+ // Edge might be installed in C:\Program Files\Edge...
+ // If file doesn't exist, check there.
+ if(!File.Exists(edgePath))
+ {
+ // Using environment variable here since EnvironMent.SpecialFolder.ProgramFiles resolves to the X86
+ // variant depending on the executable architecture. The path variable always evaluates to the correct path though.
+ edgePath = $@"{Environment.GetEnvironmentVariable("ProgramW6432")}\Microsoft\Edge\Application\msedge.exe";
+ }
+
+ options.BinaryLocation = edgePath;
+
+ var manager = new SeleniumDriverManager("msedgedriver");
+ var driverPath = manager.GetOrDownloadLatestDriverForBin(edgePath, null, /*GetDriverLatestVersion, */GetDriverUri);
+
+ var svc = EdgeDriverService.CreateDefaultService(driverPath.FullName);//.CreateDefaultServiceFromOptions(driverPath.FullName, "msedgedriver.exe", options);
+ svc.EnableVerboseLogging = true;
+
+ var driver = new EdgeDriver(svc, options);
+
+ return driver;
+ }
+
+ public static EdgeDriver FromDriverPath(string driverPath, EdgeOptions options)
+ => new EdgeDriver(driverPath, options);
+
+ private static Uri GetDriverLatestVersion(Version browserVersion) => new Uri($"https://msedgedriver.azureedge.net/LATEST_RELEASE_{browserVersion.Major}");
+ private static Uri GetDriverUri(Version browserVersion, string driverVersion) => new Uri($"https://msedgedriver.azureedge.net/{driverVersion}/edgedriver_win32.zip");
+ }
+
+ private SeleniumDriverManager(string driverName)
+ {
+ DriverName = driverName;
+ }
+
+ public string DriverName { get; }
+
+ ///
+ /// Gets the file version of the provided browser path
+ ///
+ /// Path to the browser executable
+ protected Version GetVersion(string browserPath)
+ {
+ var process = new Process();
+ process.StartInfo.FileName = "wmic.exe";
+ process.StartInfo.Arguments = $@"datafile where name=""{browserPath.Replace("\\", "\\\\")}"" get Version /value";
+ process.StartInfo.UseShellExecute = false;
+ process.StartInfo.RedirectStandardOutput = true;
+ process.StartInfo.RedirectStandardError = true;
+ process.Start();
+
+ var wincOutput = process.StandardOutput.ReadToEnd();
+ var browserRawVersion = wincOutput.Split('=').LastOrDefault()?.Trim();
+
+ if(Version.TryParse(browserRawVersion, out var browserVersion))
+ {
+ return browserVersion;
+ }
+ else
+ {
+ throw new NotSupportedException($"Unable to determine the browser version. The used path was [{browserPath}], found raw [{browserRawVersion}] parsed [{browserVersion}].");
+ }
+ }
+
+ ///
+ /// Gets the target standard install path for the given version of this driver
+ ///
+ /// The version of the driver
+ protected FileInfo GetDriverInstallPath(Version version)
+ {
+ var driverLocalPath = Path.Combine(Path.GetTempPath(), "Uno.UITests", $"{DriverName}", version.ToString());
+ Directory.CreateDirectory(driverLocalPath);
+
+ return new FileInfo(driverLocalPath + $"\\{DriverName}.exe");
+ }
+
+ ///
+ /// Download the zip package of the driver, and extract the driver executable to the target install path.
+ ///
+ /// The Uri of the package of the driver to download
+ /// The target install path of the driver
+ protected void Download(Uri driverSourceUri, FileInfo driverInstallPath)
+ {
+ var tempZipFileName = Path.GetTempFileName();
+ try
+ {
+ Console.WriteLine($"Downloading {DriverName} from [{driverSourceUri.OriginalString}]");
+ new WebClient().DownloadFile(driverSourceUri, tempZipFileName);
+
+ using(var zipFile = ZipFile.OpenRead(tempZipFileName))
+ {
+ zipFile.Entries
+ .FirstOrDefault(x => x.Name.EndsWith($"{DriverName}.exe"))?
+ .ExtractToFile(driverInstallPath.FullName, true);
+ }
+ }
+ finally
+ {
+ try
+ {
+ if(File.Exists(tempZipFileName))
+ {
+ File.Delete(tempZipFileName);
+ }
+ }
+ catch
+ { // Make sure if the file is locked process doesn't crash
+ }
+ }
+ }
+
+ protected delegate Uri GetDriverLatestVersion(Version browserVersion);
+ protected delegate Uri GetDriverUri(Version browserVersion, string driverVersion);
+
+ protected DirectoryInfo GetOrDownloadLatestDriverForBin(
+ string binPath,
+ GetDriverLatestVersion getDriverLatestVersion,
+ GetDriverUri getDriverUri)
+ {
+ if(Environment.OSVersion.Platform == PlatformID.Win32NT)
+ {
+ var version = GetVersion(binPath);
+ var driverFile = GetDriverInstallPath(version);
+
+ if(!driverFile.Exists)
+ {
+ string driverVersion;
+ if(getDriverLatestVersion == null)
+ {
+ driverVersion = version.ToString();
+ }
+ else
+ {
+ var driverLatestVersion = getDriverLatestVersion(version);
+
+ Console.WriteLine($"Fetching driver version for {DriverName} [{version}]");
+ driverVersion = new WebClient().DownloadString(driverLatestVersion).Trim();
+ }
+
+ var driverUri = getDriverUri(version, driverVersion);
+
+ Download(driverUri, driverFile);
+ }
+
+ return driverFile.Directory;
+ }
+ else
+ {
+ throw new NotSupportedException($"Unable to determine the chrome driver location. Use the {SeleniumAppConfigurator.UNO_UITEST_DRIVER_PATH} environment variable.");
+ }
+ }
+ }
+}
diff --git a/src/Uno.UITest.Puppeteer/Uno.UITest.Selenium.csproj b/src/Uno.UITest.Puppeteer/Uno.UITest.Selenium.csproj
index a0890a4..2aabcf3 100644
--- a/src/Uno.UITest.Puppeteer/Uno.UITest.Selenium.csproj
+++ b/src/Uno.UITest.Puppeteer/Uno.UITest.Selenium.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/uno.uitest.sln b/src/uno.uitest.sln
index 96d7c8a..7f7a97a 100644
--- a/src/uno.uitest.sln
+++ b/src/uno.uitest.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28902.138
+# Visual Studio Version 17
+VisualStudioVersion = 17.7.34003.232
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.UITest", "uno.uitest\Uno.UITest.csproj", "{C0578553-D584-48C3-9484-1A25DAD66269}"
EndProject
@@ -25,14 +25,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Wasm", "Sample\Sampl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.UITest.Helpers", "Uno.UITest.Helpers\Uno.UITest.Helpers.csproj", "{B233D12F-E9FD-4239-9742-A2F712752000}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C6491A5A-8CBB-493C-A8EF-EFF6815F7550}"
+ ProjectSection(SolutionItems) = preProject
+ Directory.Build.props = Directory.Build.props
+ Directory.Build.targets = Directory.Build.targets
+ EndProjectSection
+EndProject
Global
- GlobalSection(SharedMSBuildProjectFiles) = preSolution
- Sample\Sample.Shared\Sample.Shared.projitems*{336cc69a-c9d1-48d3-8029-d80d8989403e}*SharedItemsImports = 4
- Sample\Sample.Shared\Sample.Shared.projitems*{34403e87-8f3e-40c3-8ece-7f3873bcd693}*SharedItemsImports = 4
- Sample\Sample.Shared\Sample.Shared.projitems*{6279c845-92f8-4333-ab99-3d213163593c}*SharedItemsImports = 13
- Sample\Sample.Shared\Sample.Shared.projitems*{74eebcc9-f40e-422b-8c39-ca6e674301ae}*SharedItemsImports = 5
- Sample\Sample.Shared\Sample.Shared.projitems*{dbce58a5-d2d6-4912-abd4-aa7e12519361}*SharedItemsImports = 4
- EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
Ad-Hoc|ARM = Ad-Hoc|ARM
@@ -525,4 +524,11 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8151C9E9-B16D-46FF-B9AB-8E19B32E1B4F}
EndGlobalSection
+ GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ Sample\Sample.Shared\Sample.Shared.projitems*{336cc69a-c9d1-48d3-8029-d80d8989403e}*SharedItemsImports = 4
+ Sample\Sample.Shared\Sample.Shared.projitems*{34403e87-8f3e-40c3-8ece-7f3873bcd693}*SharedItemsImports = 4
+ Sample\Sample.Shared\Sample.Shared.projitems*{6279c845-92f8-4333-ab99-3d213163593c}*SharedItemsImports = 13
+ Sample\Sample.Shared\Sample.Shared.projitems*{74eebcc9-f40e-422b-8c39-ca6e674301ae}*SharedItemsImports = 5
+ Sample\Sample.Shared\Sample.Shared.projitems*{dbce58a5-d2d6-4912-abd4-aa7e12519361}*SharedItemsImports = 4
+ EndGlobalSection
EndGlobal