diff --git a/Winium/TestApp.Test/py-functional/tests/test_commands.py b/Winium/TestApp.Test/py-functional/tests/test_commands.py index 066dece..cfccdf1 100644 --- a/Winium/TestApp.Test/py-functional/tests/test_commands.py +++ b/Winium/TestApp.Test/py-functional/tests/test_commands.py @@ -1,5 +1,6 @@ # coding: utf-8 from time import sleep + import pytest from selenium.common.exceptions import NoSuchElementException, NoAlertPresentException, WebDriverException from selenium.webdriver import ActionChains @@ -9,7 +10,6 @@ from tests import WuaTestCase - By.XNAME = 'xname' @@ -17,6 +17,7 @@ class TestGetCommands(WuaTestCase): """ Test GET commands that do not change anything in app, meaning they can all be run in one session. """ + def test_get_current_window_handle(self): """ GET /session/:sessionId/window_handle Retrieve the current window handle. @@ -108,8 +109,9 @@ def test_get_element_text(self): @pytest.mark.parametrize(("attr_name", "expected_value"), [ ('Width', '300'), ('DesiredSize.Width', '300'), - ('AutomationIdProperty', 'MyTextBox') - ], ids=['simple property', 'nested property', 'automation property']) + ('AutomationIdProperty', 'MyTextBox'), + ('Visibility', '0'), + ], ids=['simple property', 'nested property', 'automation property', 'enum']) def test_get_element_attribute(self, attr_name, expected_value): """ GET /session/:sessionId/element/:id/attribute/:name Get the value of an element's attribute. @@ -252,7 +254,7 @@ def test_automation_toggle(self): assert start_state != end_state - @pytest.mark.parametrize(("attribute", "value"), [('Width', 10, ), ('Background.Opacity', 0, )], + @pytest.mark.parametrize(("attribute", "value"), [('Width', 10,), ('Background.Opacity', 0,)], ids=["should set basic properties", "should set nested properties"]) def test_attribute_set(self, attribute, value): element = self.driver.find_element_by_id('SetButton') diff --git a/Winium/TestApp.Test/py-functional/tests/test_element_attribute_settings.py b/Winium/TestApp.Test/py-functional/tests/test_element_attribute_settings.py new file mode 100644 index 0000000..b9beae6 --- /dev/null +++ b/Winium/TestApp.Test/py-functional/tests/test_element_attribute_settings.py @@ -0,0 +1,60 @@ +# coding: utf-8 +from copy import copy + +import pytest +from selenium.webdriver import Remote + +import config + + +@pytest.yield_fixture() +def driver(request): + capabilities = request.param + winium = Remote(command_executor="http://localhost:9999", desired_capabilities=capabilities) + yield winium + winium.quit() + + +def _enum_as_string_settings_capability(value): + caps = copy(config.DESIRED_CAPABILITIES) + caps['commandSettings'] = {'elementAttributeSettings': {'enumAsString': value}} + return caps + + +def _access_modifier_settings_capability(value): + caps = copy(config.DESIRED_CAPABILITIES) + caps['commandSettings'] = {'elementAttributeSettings': {'accessModifier': value}} + return caps + + +class TestElementAttributeCommandSettings(object): + @pytest.mark.parametrize('driver', [_enum_as_string_settings_capability(False)], indirect=True) + def test_get_element_attribute_enum_as_value(self, driver): + element = driver.find_element_by_id('MyTextBox') + element.get_attribute('DesiredSize') + value = element.get_attribute('Visibility') + assert '0' == value + + @pytest.mark.parametrize('driver', [_enum_as_string_settings_capability(True)], indirect=True) + def test_get_element_attribute_enum_as_string(self, driver): + element = driver.find_element_by_id('MyTextBox') + value = element.get_attribute('Visibility') + assert 'Visible' == value + + @pytest.mark.parametrize('driver', [ + _access_modifier_settings_capability('AutomationProperties'), + _access_modifier_settings_capability('DependencyProperties'), + _access_modifier_settings_capability('ClrProperties'), + ], indirect=True) + def test_get_element_attribute_access_modifier(self, driver): + expected = { + 'AutomationProperties': ['MyTextBox', None, None], + 'DependencyProperties': ['MyTextBox', 'false', None], + 'ClrProperties': ['MyTextBox', 'false', '300'], + }[driver.desired_capabilities['commandSettings']['elementAttributeSettings']['accessModifier']] + + element = driver.find_element_by_id('MyTextBox') + + for i, attr in enumerate(['AutomationIdProperty', 'IsReadOnlyProperty', 'Width']): + value = element.get_attribute(attr) + assert expected[i] == value diff --git a/Winium/Winium.StoreApps.Common/Command.cs b/Winium/Winium.StoreApps.Common/Command.cs index 82333a2..a8a7d76 100644 --- a/Winium/Winium.StoreApps.Common/Command.cs +++ b/Winium/Winium.StoreApps.Common/Command.cs @@ -11,17 +11,11 @@ public class Command { - #region Fields - - private IDictionary commandParameters = new JObject(); - - #endregion - #region Constructors and Destructors public Command(string name, IDictionary parameters) + : this(name) { - this.Name = name; if (parameters != null) { this.Parameters = parameters; @@ -35,6 +29,7 @@ public Command(string name, string jsonParameters) public Command(string name) { + this.Parameters = new JObject(); this.Name = name; } @@ -56,18 +51,7 @@ public Command() /// Gets the parameters of the command /// [JsonProperty("parameters")] - public IDictionary Parameters - { - get - { - return this.commandParameters; - } - - set - { - this.commandParameters = value; - } - } + public IDictionary Parameters { get; set; } /// /// Gets the SessionID of the command diff --git a/Winium/Winium.StoreApps.Common/CommandSettings/CommandSettings.cs b/Winium/Winium.StoreApps.Common/CommandSettings/CommandSettings.cs new file mode 100644 index 0000000..8dd5260 --- /dev/null +++ b/Winium/Winium.StoreApps.Common/CommandSettings/CommandSettings.cs @@ -0,0 +1,33 @@ +namespace Winium.StoreApps.Common.CommandSettings +{ + #region + + using Newtonsoft.Json; + + #endregion + + public class CommandSettings + { + #region Constants + + public const string ElementAttributeSettingsParameter = "ElementAttributeSettings"; + + #endregion + + #region Constructors and Destructors + + public CommandSettings() + { + this.ElementAttributeSettings = new ElementAttributeCommandSettings(); + } + + #endregion + + #region Public Properties + + [JsonProperty("elementAttributeSettings")] + public ElementAttributeCommandSettings ElementAttributeSettings { get; set; } + + #endregion + } +} diff --git a/Winium/Winium.StoreApps.Common/CommandSettings/ElementAttributeCommandSettings.cs b/Winium/Winium.StoreApps.Common/CommandSettings/ElementAttributeCommandSettings.cs new file mode 100644 index 0000000..3c70a9f --- /dev/null +++ b/Winium/Winium.StoreApps.Common/CommandSettings/ElementAttributeCommandSettings.cs @@ -0,0 +1,44 @@ +namespace Winium.StoreApps.Common.CommandSettings +{ + #region + + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + #endregion + + public enum ElementAttributeAccessModifier + { + None = 0, + + AutomationProperties = 1, + + DependencyProperties = 3, + + ClrProperties = 7, + } + + public class ElementAttributeCommandSettings + { + #region Constructors and Destructors + + public ElementAttributeCommandSettings() + { + this.AccessModifier = ElementAttributeAccessModifier.ClrProperties; + this.EnumAsString = false; + } + + #endregion + + #region Public Properties + + [JsonProperty("accessModifier")] + [JsonConverter(typeof(StringEnumConverter))] + public ElementAttributeAccessModifier AccessModifier { get; set; } + + [JsonProperty("enumAsString")] + public bool EnumAsString { get; set; } + + #endregion + } +} diff --git a/Winium/Winium.StoreApps.Common/Winium.StoreApps.Common.csproj b/Winium/Winium.StoreApps.Common/Winium.StoreApps.Common.csproj index c65339e..5641197 100644 --- a/Winium/Winium.StoreApps.Common/Winium.StoreApps.Common.csproj +++ b/Winium/Winium.StoreApps.Common/Winium.StoreApps.Common.csproj @@ -39,11 +39,13 @@ + + diff --git a/Winium/Winium.StoreApps.Driver/Automator/Capabilities.cs b/Winium/Winium.StoreApps.Driver/Automator/Capabilities.cs index 585c38b..d96de90 100644 --- a/Winium/Winium.StoreApps.Driver/Automator/Capabilities.cs +++ b/Winium/Winium.StoreApps.Driver/Automator/Capabilities.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json.Serialization; using Winium.Mobile.Connectivity; + using Winium.StoreApps.Common.CommandSettings; using Winium.StoreApps.Common.Exceptions; #endregion @@ -35,6 +36,7 @@ internal Capabilities() this.Dependencies = new List(); this.PingTimeout = DefaultPingTimeout; this.NoFallback = true; + this.CommandSettings = new CommandSettings(); } #endregion @@ -110,6 +112,9 @@ public static string PlatformName [JsonProperty("takesScreenshot")] public bool TakesScreenshot { get; set; } + [JsonProperty("commandSettings")] + public CommandSettings CommandSettings { get; set; } + #endregion #region Public Methods and Operators diff --git a/Winium/Winium.StoreApps.Driver/CommandExecutors/GetElementAttributeExecutor.cs b/Winium/Winium.StoreApps.Driver/CommandExecutors/GetElementAttributeExecutor.cs new file mode 100644 index 0000000..503ac72 --- /dev/null +++ b/Winium/Winium.StoreApps.Driver/CommandExecutors/GetElementAttributeExecutor.cs @@ -0,0 +1,26 @@ +namespace Winium.StoreApps.Driver.CommandExecutors +{ + #region + + using Newtonsoft.Json.Linq; + + using Winium.StoreApps.Common.CommandSettings; + + #endregion + + internal class GetElementAttributeExecutor : CommandExecutorBase + { + #region Methods + + protected override string DoImpl() + { + var settings = JToken.FromObject(this.Automator.ActualCapabilities.CommandSettings.ElementAttributeSettings); + this.ExecutedCommand.Parameters.Add(CommandSettings.ElementAttributeSettingsParameter, settings); + var response = this.Automator.CommandForwarder.ForwardCommand(this.ExecutedCommand); + + return response; + } + + #endregion + } +} diff --git a/Winium/Winium.StoreApps.Driver/Winium.StoreApps.Driver.csproj b/Winium/Winium.StoreApps.Driver/Winium.StoreApps.Driver.csproj index 0e416b8..72c5a3b 100644 --- a/Winium/Winium.StoreApps.Driver/Winium.StoreApps.Driver.csproj +++ b/Winium/Winium.StoreApps.Driver/Winium.StoreApps.Driver.csproj @@ -53,6 +53,7 @@ + diff --git a/Winium/Winium.StoreApps.InnerServer/Commands/GetElementAttributeCommand.cs b/Winium/Winium.StoreApps.InnerServer/Commands/GetElementAttributeCommand.cs index 1c9dd0f..06b6d62 100644 --- a/Winium/Winium.StoreApps.InnerServer/Commands/GetElementAttributeCommand.cs +++ b/Winium/Winium.StoreApps.InnerServer/Commands/GetElementAttributeCommand.cs @@ -6,9 +6,12 @@ using System.Reflection; using Newtonsoft.Json; - using Newtonsoft.Json.Linq; + using Newtonsoft.Json.Converters; using Winium.StoreApps.Common; + using Winium.StoreApps.Common.CommandSettings; + using Winium.StoreApps.InnerServer.Commands.Helpers; + using Winium.StoreApps.InnerServer.Element; #endregion @@ -26,30 +29,59 @@ protected override string DoImpl() { var element = this.Automator.ElementsRegistry.GetRegisteredElement(this.ElementId); - JToken value; - string attributeName = null; - if (this.Parameters.TryGetValue("NAME", out value)) + string attributeName; + if (!this.Parameters.TryGetValue("NAME", out attributeName)) { - attributeName = value.ToString(); + return this.JsonResponse(); } - if (attributeName == null) + ElementAttributeCommandSettings settings; + if (!this.Parameters.TryGetValue(CommandSettings.ElementAttributeSettingsParameter, out settings)) { - return this.JsonResponse(); + settings = new ElementAttributeCommandSettings(); } /* GetAttribute command should return: null if no property was found, * property value as plain string if property is scalar or string, * JSON encoded property if property is Lists, Dictionary or other nonscalar types */ + var value = GetPropertyCascade(element, attributeName, settings.AccessModifier); + + return this.JsonResponse(ResponseStatus.Success, SerializeObjectAsString(value, settings.EnumAsString)); + } + + private static object GetPropertyCascade( + WiniumElement element, + string key, + ElementAttributeAccessModifier options) + { object propertyObject; + if (element.TryGetAutomationProperty(key, out propertyObject)) + { + return propertyObject; + } + + if (options == ElementAttributeAccessModifier.AutomationProperties) + { + return null; + } - if (!element.TryGetAutomationProperty(attributeName, out propertyObject)) + if (element.TryGetDependencyProperty(key, out propertyObject)) { - element.TryGetProperty(attributeName, out propertyObject); + return propertyObject; } - return this.JsonResponse(ResponseStatus.Success, SerializeObjectAsString(propertyObject)); + if (options == ElementAttributeAccessModifier.DependencyProperties) + { + return null; + } + + if (element.TryGetProperty(key, out propertyObject)) + { + return propertyObject; + } + + return null; } private static bool IsTypeSerializedUsingToString(Type type) @@ -58,7 +90,7 @@ private static bool IsTypeSerializedUsingToString(Type type) return type == typeof(string) || type.GetTypeInfo().IsPrimitive; } - private static string SerializeObjectAsString(object obj) + private static string SerializeObjectAsString(object obj, bool enumAsString) { if (obj == null) { @@ -71,8 +103,19 @@ private static string SerializeObjectAsString(object obj) return obj.ToString(); } + if (obj is Enum) + { + return (obj as Enum).ToString(enumAsString ? "G" : "D"); + } + // Serialize other data types as JSON - return JsonConvert.SerializeObject(obj); + var settings = new JsonSerializerSettings(); + if (enumAsString) + { + settings.Converters.Add(new StringEnumConverter()); + } + + return JsonConvert.SerializeObject(obj, settings); } #endregion diff --git a/Winium/Winium.StoreApps.InnerServer/Commands/Helpers/IDictionaryExtensions.cs b/Winium/Winium.StoreApps.InnerServer/Commands/Helpers/IDictionaryExtensions.cs new file mode 100644 index 0000000..bff6758 --- /dev/null +++ b/Winium/Winium.StoreApps.InnerServer/Commands/Helpers/IDictionaryExtensions.cs @@ -0,0 +1,30 @@ +namespace Winium.StoreApps.InnerServer.Commands.Helpers +{ + #region + + using System.Collections.Generic; + + using Newtonsoft.Json.Linq; + + #endregion + + public static class DictionaryExtensions + { + #region Public Methods and Operators + + public static bool TryGetValue(this IDictionary dictionary, string key, out T value) + { + JToken jsonValue; + value = default(T); + if (!dictionary.TryGetValue(key, out jsonValue)) + { + return false; + } + + value = jsonValue.ToObject(); + return true; + } + + #endregion + } +} diff --git a/Winium/Winium.StoreApps.InnerServer/Element/Helpers/PropertiesAccessor.cs b/Winium/Winium.StoreApps.InnerServer/Element/Helpers/PropertiesAccessor.cs index c877e8c..e95586c 100644 --- a/Winium/Winium.StoreApps.InnerServer/Element/Helpers/PropertiesAccessor.cs +++ b/Winium/Winium.StoreApps.InnerServer/Element/Helpers/PropertiesAccessor.cs @@ -33,6 +33,26 @@ public static void SetProperty(FrameworkElement element, string propertyName, JT } } + public static bool TryGetDependencyProperty(FrameworkElement element, string propertyName, out object value) + { + value = null; + var propertyInfo = + element.GetType() + .GetRuntimeProperties() + .Where(x => x.PropertyType == typeof(DependencyProperty)) + .FirstOrDefault(x => x.Name == propertyName); + + if (propertyInfo == null) + { + return false; + } + + var dp = propertyInfo.GetValue(null) as DependencyProperty; + value = element.GetValue(dp); + + return true; + } + public static bool TryGetProperty(FrameworkElement element, string propertyName, out object value) { value = null; diff --git a/Winium/Winium.StoreApps.InnerServer/Element/WiniumElement.cs b/Winium/Winium.StoreApps.InnerServer/Element/WiniumElement.cs index cf5c3e1..310dce4 100644 --- a/Winium/Winium.StoreApps.InnerServer/Element/WiniumElement.cs +++ b/Winium/Winium.StoreApps.InnerServer/Element/WiniumElement.cs @@ -151,6 +151,11 @@ public bool TryGetAutomationProperty(string automationPropertyName, out object v out value); } + public bool TryGetDependencyProperty(string propertyName, out object value) + { + return PropertiesAccessor.TryGetDependencyProperty(this.Element, propertyName, out value); + } + public bool TryGetProperty(string attributeName, out object value) { return PropertiesAccessor.TryGetProperty(this.Element, attributeName, out value); diff --git a/Winium/Winium.StoreApps.InnerServer/Winium.StoreApps.InnerServer.csproj b/Winium/Winium.StoreApps.InnerServer/Winium.StoreApps.InnerServer.csproj index 81a3143..b61c631 100644 --- a/Winium/Winium.StoreApps.InnerServer/Winium.StoreApps.InnerServer.csproj +++ b/Winium/Winium.StoreApps.InnerServer/Winium.StoreApps.InnerServer.csproj @@ -48,6 +48,7 @@ +