From 8da114053fa016c3e480fa21e11d3eb99e035954 Mon Sep 17 00:00:00 2001 From: aristotelos Date: Tue, 23 Apr 2024 15:08:39 +0200 Subject: [PATCH 1/3] Require appium:automationName Add required capability appium:automationName equal to FlaUI. --- README.md | 3 ++ src/FlaUI.WebDriver.UITests/SessionTests.cs | 28 +++++++++++++------ .../TestUtil/FlaUIDriverOptions.cs | 3 ++ .../Controllers/SessionController.cs | 12 +++++++- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e834017..35cd349 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ The following capabilities are supported: | Capability Name | Description | Example value | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | platformName | Must be set to `windows` (case-insensitive). | `windows` | +| appium:automationName | Must be set to `FlaUI` (case-insensitive). | `FlaUI` | | appium:app | The path to the application, or in case of an UWP app, `!App`. It is also possible to set app to `Root`. In such case the session will be invoked without any explicit target application. Either this capability, `appTopLevelWindow` or `appTopLevelWindowTitleMatch` must be provided on session startup. | `C:\Windows\System32\notepad.exe`, `Microsoft.WindowsCalculator_8wekyb3d8bbwe!App` | | appium:appArguments | Application arguments string, for example `/?`. | | | appium:appWorkingDir | Full path to the folder, which is going to be set as the working dir for the application under test. This is only applicable for classic apps. When this is used the `appium:app` may contain a relative file path. | `C:\MyApp\` | @@ -57,6 +58,7 @@ public class FlaUIDriverOptions : DriverOptions { PlatformName = "windows" }; + options.AddAdditionalOption("appium:automationName", "flaui"); options.AddAdditionalOption("appium:app", path); return options; } @@ -78,6 +80,7 @@ import { remote } from 'webdriverio' const driver = await remote({ capabilities: { platformName: 'windows', + 'appium:automationName': 'flaui' 'appium:app': 'C:\\YourApp.exe' } }); diff --git a/src/FlaUI.WebDriver.UITests/SessionTests.cs b/src/FlaUI.WebDriver.UITests/SessionTests.cs index 76e66c5..df6ac39 100644 --- a/src/FlaUI.WebDriver.UITests/SessionTests.cs +++ b/src/FlaUI.WebDriver.UITests/SessionTests.cs @@ -16,7 +16,18 @@ public void NewSession_PlatformNameMissing_ReturnsError() var newSession = () => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, emptyOptions); - Assert.That(newSession, Throws.TypeOf().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)")); + Assert.That(newSession, Throws.TypeOf().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, capability 'appium:automationName' with value `FlaUI` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)")); + } + + [Test] + public void NewSession_AutomationNameMissing_ReturnsError() + { + var emptyOptions = FlaUIDriverOptions.Empty(); + emptyOptions.AddAdditionalOption("appium:platformName", "windows"); + + var newSession = () => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, emptyOptions); + + Assert.That(newSession, Throws.TypeOf().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, capability 'appium:automationName' with value `FlaUI` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)")); } [Test] @@ -24,9 +35,11 @@ public void NewSession_AllAppCapabilitiesMissing_ReturnsError() { var emptyOptions = FlaUIDriverOptions.Empty(); emptyOptions.AddAdditionalOption("appium:platformName", "windows"); + emptyOptions.AddAdditionalOption("appium:automationName", "windows"); + var newSession = () => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, emptyOptions); - Assert.That(newSession, Throws.TypeOf().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)")); + Assert.That(newSession, Throws.TypeOf().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, capability 'appium:automationName' with value `FlaUI` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)")); } [Test] @@ -58,6 +71,7 @@ public void NewSession_AppNotAString_Throws(object value) { PlatformName = "Windows" }; + driverOptions.AddAdditionalOption("appium:automationName", "FlaUI"); driverOptions.AddAdditionalOption("appium:app", value); Assert.That(() => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions), @@ -83,7 +97,7 @@ public void NewSession_NotSupportedCapability_Throws() driverOptions.AddAdditionalOption("unknown:unknown", "value"); Assert.That(() => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions), - Throws.TypeOf().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)")); + Throws.TypeOf().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, capability 'appium:automationName' with value `FlaUI` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)")); } [Test] @@ -179,6 +193,7 @@ public void NewSession_AppTopLevelWindowTitleMatchNotAString_Throws(object value { PlatformName = "Windows" }; + driverOptions.AddAdditionalOption("appium:automationName", "FlaUI"); driverOptions.AddAdditionalOption("appium:appTopLevelWindowTitleMatch", value); Assert.That(() => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions), @@ -188,11 +203,7 @@ public void NewSession_AppTopLevelWindowTitleMatchNotAString_Throws(object value [TestCase("(invalid")] public void NewSession_AppTopLevelWindowTitleMatchInvalidRegex_Throws(string value) { - var driverOptions = new FlaUIDriverOptions() - { - PlatformName = "Windows" - }; - driverOptions.AddAdditionalOption("appium:appTopLevelWindowTitleMatch", value); + var driverOptions = FlaUIDriverOptions.AppTopLevelWindowTitleMatch(value); Assert.That(() => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions), Throws.TypeOf().With.Message.EqualTo("Capability appium:appTopLevelWindowTitleMatch '(invalid' is not a valid regular expression: Invalid pattern '(invalid' at offset 8. Not enough )'s.")); @@ -217,6 +228,7 @@ public void NewSession_AppTopLevelWindowNotAString_ReturnsError(object value) { PlatformName = "Windows" }; + driverOptions.AddAdditionalOption("appium:automationName", "FlaUI"); driverOptions.AddAdditionalOption("appium:appTopLevelWindow", value); Assert.That(() => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions), diff --git a/src/FlaUI.WebDriver.UITests/TestUtil/FlaUIDriverOptions.cs b/src/FlaUI.WebDriver.UITests/TestUtil/FlaUIDriverOptions.cs index e4421bc..ac1fb82 100644 --- a/src/FlaUI.WebDriver.UITests/TestUtil/FlaUIDriverOptions.cs +++ b/src/FlaUI.WebDriver.UITests/TestUtil/FlaUIDriverOptions.cs @@ -20,6 +20,7 @@ public static FlaUIDriverOptions App(string path) { PlatformName = "Windows" }; + options.AddAdditionalOption("appium:automationName", "FlaUI"); options.AddAdditionalOption("appium:app", path); return options; } @@ -30,6 +31,7 @@ public static DriverOptions AppTopLevelWindow(string windowHandle) { PlatformName = "Windows" }; + options.AddAdditionalOption("appium:automationName", "FlaUI"); options.AddAdditionalOption("appium:appTopLevelWindow", windowHandle); return options; } @@ -40,6 +42,7 @@ public static DriverOptions AppTopLevelWindowTitleMatch(string match) { PlatformName = "Windows" }; + options.AddAdditionalOption("appium:automationName", "FlaUI"); options.AddAdditionalOption("appium:appTopLevelWindowTitleMatch", match); return options; } diff --git a/src/FlaUI.WebDriver/Controllers/SessionController.cs b/src/FlaUI.WebDriver/Controllers/SessionController.cs index 6cef097..b1c0d2e 100644 --- a/src/FlaUI.WebDriver/Controllers/SessionController.cs +++ b/src/FlaUI.WebDriver/Controllers/SessionController.cs @@ -42,7 +42,7 @@ public async Task CreateNewSession([FromBody] CreateSessionRequest return WebDriverResult.Error(new ErrorResponse { ErrorCode = "session not created", - Message = "Required capabilities did not match. Capability `platformName` with value `windows` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability" + Message = "Required capabilities did not match. Capability `platformName` with value `windows` is required, capability 'appium:automationName' with value `FlaUI` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability" }); } if (TryGetStringCapability(capabilities, "appium:app", out var appPath)) @@ -117,6 +117,16 @@ private bool IsMatchingCapabilitySet(IDictionary capabiliti return false; } + if (TryGetStringCapability(capabilities, "appium:automationName", out var automationName) + && automationName.ToLowerInvariant() == "flaui") + { + matchedCapabilities.Add("appium:automationName", capabilities["appium:automationName"]); + } + else + { + return false; + } + if (TryGetStringCapability(capabilities, "appium:app", out var appPath)) { matchedCapabilities.Add("appium:app", capabilities["appium:app"]); From a0340cd5da67876c47970fd1257c2977aba973bc Mon Sep 17 00:00:00 2001 From: aristotelos Date: Tue, 23 Apr 2024 15:09:07 +0200 Subject: [PATCH 2/3] Add timeouts capability --- src/FlaUI.WebDriver.UITests/SessionTests.cs | 18 ++++++++++++++++++ .../Controllers/SessionController.cs | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/FlaUI.WebDriver.UITests/SessionTests.cs b/src/FlaUI.WebDriver.UITests/SessionTests.cs index df6ac39..f7f8cdd 100644 --- a/src/FlaUI.WebDriver.UITests/SessionTests.cs +++ b/src/FlaUI.WebDriver.UITests/SessionTests.cs @@ -3,6 +3,7 @@ using FlaUI.WebDriver.UITests.TestUtil; using OpenQA.Selenium; using System; +using System.Collections.Generic; namespace FlaUI.WebDriver.UITests { @@ -90,6 +91,23 @@ public void NewSession_AppWorkingDir_IsSupported() Assert.That(title, Is.EqualTo("FlaUI WPF Test App")); } + [Test] + public void NewSession_Timeouts_IsSupported() + { + var driverOptions = FlaUIDriverOptions.TestApp(); + driverOptions.AddAdditionalOption("timeouts", new Dictionary() + { + ["script"] = 10000, + ["pageLoad"] = 50000, + ["implicit"] = 3000 + }); + using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions); + + Assert.That(driver.Manage().Timeouts().AsynchronousJavaScript, Is.EqualTo(TimeSpan.FromSeconds(10))); + Assert.That(driver.Manage().Timeouts().PageLoad, Is.EqualTo(TimeSpan.FromSeconds(50))); + Assert.That(driver.Manage().Timeouts().ImplicitWait, Is.EqualTo(TimeSpan.FromSeconds(3))); + } + [Test] public void NewSession_NotSupportedCapability_Throws() { diff --git a/src/FlaUI.WebDriver/Controllers/SessionController.cs b/src/FlaUI.WebDriver/Controllers/SessionController.cs index b1c0d2e..3a2db06 100644 --- a/src/FlaUI.WebDriver/Controllers/SessionController.cs +++ b/src/FlaUI.WebDriver/Controllers/SessionController.cs @@ -95,6 +95,15 @@ public async Task CreateNewSession([FromBody] CreateSessionRequest { session.NewCommandTimeout = TimeSpan.FromSeconds(newCommandTimeout); } + if (capabilities.TryGetValue("timeouts", out var valueJson)) + { + var timeoutsConfiguration = JsonSerializer.Deserialize(valueJson); + if (timeoutsConfiguration == null) + { + throw WebDriverResponseException.InvalidArgument("Could not deserialize timeouts capability"); + } + session.TimeoutsConfiguration = timeoutsConfiguration; + } _sessionRepository.Add(session); _logger.LogInformation("Created session with ID {SessionId} and capabilities {Capabilities}", session.SessionId, capabilities); return await Task.FromResult(WebDriverResult.Success(new CreateSessionResponse() @@ -159,11 +168,16 @@ private bool IsMatchingCapabilitySet(IDictionary capabiliti return false; } - if (TryGetNumberCapability(capabilities, "appium:newCommandTimeout", out _)) + if (capabilities.ContainsKey("appium:newCommandTimeout")) { matchedCapabilities.Add("appium:newCommandTimeout", capabilities["appium:newCommandTimeout"]); ; } + if (capabilities.ContainsKey("timeouts")) + { + matchedCapabilities.Add("timeouts", capabilities["timeouts"]); + } + return matchedCapabilities.Count == capabilities.Count; } From fc2ab29751a14e27de0161c5db9a966d74158524 Mon Sep 17 00:00:00 2001 From: aristotelos Date: Tue, 23 Apr 2024 15:10:17 +0200 Subject: [PATCH 3/3] Simplify capability matching Remove type restrictions on capability matching to remove duplicate code. --- src/FlaUI.WebDriver/Controllers/SessionController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FlaUI.WebDriver/Controllers/SessionController.cs b/src/FlaUI.WebDriver/Controllers/SessionController.cs index 3a2db06..617faf5 100644 --- a/src/FlaUI.WebDriver/Controllers/SessionController.cs +++ b/src/FlaUI.WebDriver/Controllers/SessionController.cs @@ -142,24 +142,24 @@ private bool IsMatchingCapabilitySet(IDictionary capabiliti if (appPath != "Root") { - if(TryGetStringCapability(capabilities, "appium:appArguments", out _)) + if(capabilities.ContainsKey("appium:appArguments")) { matchedCapabilities.Add("appium:appArguments", capabilities["appium:appArguments"]); } if (!appPath.EndsWith("!App")) { - if (TryGetStringCapability(capabilities, "appium:appWorkingDir", out _)) + if (capabilities.ContainsKey("appium:appWorkingDir")) { matchedCapabilities.Add("appium:appWorkingDir", capabilities["appium:appWorkingDir"]); } } } } - else if (TryGetStringCapability(capabilities, "appium:appTopLevelWindow", out _)) + else if (capabilities.ContainsKey("appium:appTopLevelWindow")) { matchedCapabilities.Add("appium:appTopLevelWindow", capabilities["appium:appTopLevelWindow"]); } - else if (TryGetStringCapability(capabilities, "appium:appTopLevelWindowTitleMatch", out _)) + else if (capabilities.ContainsKey("appium:appTopLevelWindowTitleMatch")) { matchedCapabilities.Add("appium:appTopLevelWindowTitleMatch", capabilities["appium:appTopLevelWindowTitleMatch"]); }