From 43f86a98056de36e7a9bd65722ab06990a183226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Fri, 20 Sep 2019 12:14:34 +0300 Subject: [PATCH] Feature/keyboard and mouse actions (#12) +semver: feature * Add KeyboardActions for Application and Element to resolve #9 * Add localization for new actions. * Refactor ApplicationManager and Factories. Add setters to Application and ServiceProvider properties. Rework container registration * Implemented MouseActions for custom element and for the whole application to resolve #8 . Add Scroll as mouse action to resolve #14 * Add xml documentation to library. Fix documentation for some classes. --- .../Actions/ApplicationActions.cs | 47 ++ .../Actions/IKeyboardActions.cs | 34 ++ .../Actions/IMouseActions.cs | 47 ++ .../Actions/KeyboardActions.cs | 42 ++ .../Actions/ModifierKey.cs | 56 ++ .../Actions/MouseActions.cs | 63 ++ .../Applications/Application.cs | 20 +- .../Applications/ApplicationFactory.cs | 26 +- .../Applications/ApplicationManager.cs | 81 ++- .../Applications/LocalApplicationFactory.cs | 20 +- .../Applications/RemoteApplicationFactory.cs | 19 +- .../Aquality.WinAppDriver.csproj | 7 +- .../Aquality.WinAppDriver.xml | 571 ++++++++++++++++++ .../Configurations/ApplicationProfile.cs | 1 + .../Elements/Actions/ElementActions.cs | 61 ++ .../Elements/Actions/IMouseActions.cs | 64 ++ .../Elements/Actions/KeyboardActions.cs | 53 ++ .../Elements/Actions/MouseActions.cs | 114 ++++ .../Aquality.WinAppDriver/Elements/Element.cs | 10 +- .../Elements/Interfaces/IElement.cs | 16 +- .../Elements/Interfaces/IElementFactory.cs | 1 - .../Extensions/ElementExtensions.cs | 30 + .../Resources/Localization/be.json | 19 +- .../Resources/Localization/en.json | 19 +- .../Resources/Localization/ru.json | 19 +- .../Aquality.WinAppDriver/Windows/Window.cs | 4 +- .../Actions/KeyboardActionsTests.cs | 64 ++ .../Actions/MouseActionsTests.cs | 31 + .../Applications/KeyboardActionsTests.cs | 10 + .../Applications/Locators/CalculatorWindow.cs | 2 + .../Applications/MouseActionsTests.cs | 10 + .../Elements/Actions/KeyboardActionsTests.cs | 9 + .../Elements/Actions/MouseActionsTests.cs | 32 + .../Localization/LocalizationFilesTest.cs | 38 +- 34 files changed, 1554 insertions(+), 86 deletions(-) create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/ApplicationActions.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/IKeyboardActions.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/IMouseActions.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/KeyboardActions.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/ModifierKey.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/MouseActions.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.xml create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/ElementActions.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/IMouseActions.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/KeyboardActions.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/MouseActions.cs create mode 100644 Aquality.WinAppDriver/src/Aquality.WinAppDriver/Extensions/ElementExtensions.cs create mode 100644 Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Actions/KeyboardActionsTests.cs create mode 100644 Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Actions/MouseActionsTests.cs create mode 100644 Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/KeyboardActionsTests.cs create mode 100644 Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/MouseActionsTests.cs create mode 100644 Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Elements/Actions/KeyboardActionsTests.cs create mode 100644 Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Elements/Actions/MouseActionsTests.cs diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/ApplicationActions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/ApplicationActions.cs new file mode 100644 index 0000000..feca2e5 --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/ApplicationActions.cs @@ -0,0 +1,47 @@ +using Aquality.Selenium.Core.Localization; +using OpenQA.Selenium.Appium.Windows; +using System; +using SeleniumActions = OpenQA.Selenium.Interactions.Actions; + +namespace Aquality.WinAppDriver.Actions +{ + /// + /// Abstract class for any actions against the whole application. + /// + public abstract class ApplicationActions + { + private readonly LocalizationLogger localizationLogger; + private readonly Func> windowsDriverSupplier; + + + /// + /// Instantiates Aplication actions. + /// + /// Logger for localized values. + /// Method to get current application session. + protected ApplicationActions(LocalizationLogger localizationLogger, Func> windowsDriverSupplier) + { + this.localizationLogger = localizationLogger; + this.windowsDriverSupplier = windowsDriverSupplier; + } + + /// + /// Performs submitted action against new object. + /// + /// Action to be performed. + protected virtual void PerformAction(Func action) + { + action(new SeleniumActions(windowsDriverSupplier())).Build().Perform(); + } + + /// + /// Logs keyboard action in specific format. + /// + /// Key of the localized message. + /// Arguments for the localized message. + protected virtual void LogAction(string messageKey, params object[] args) + { + localizationLogger.Info(messageKey, args); + } + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/IKeyboardActions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/IKeyboardActions.cs new file mode 100644 index 0000000..83c8b40 --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/IKeyboardActions.cs @@ -0,0 +1,34 @@ +namespace Aquality.WinAppDriver.Actions +{ + /// + /// Provides methods representing basic keyboard actions. + /// + public interface IKeyboardActions + { + /// + /// Presses a key. + /// + /// The value representing the key to press. + void PressKey(ModifierKey keyToPress); + + /// + /// Releases a key. + /// + /// The value representing the key to release. + void ReleaseKey(ModifierKey keyToRelease); + + /// + /// Sends a sequence of keystrokes to the target. + /// + /// A string representing the keystrokes to send. + void SendKeys(string keySequence); + + /// + /// Sends a sequence of keystrokes to the application, holding a specified key. + /// After the action, holded key is released. + /// + /// A string representing the keystrokes to send. + /// The value representing the key to hold. + void SendKeysWithKeyHold(string keySequence, ModifierKey keyToHold); + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/IMouseActions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/IMouseActions.cs new file mode 100644 index 0000000..b222bbd --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/IMouseActions.cs @@ -0,0 +1,47 @@ +namespace Aquality.WinAppDriver.Actions +{ + /// + /// Provides methods representing basic mouse actions. + /// + public interface IMouseActions + { + /// + /// Clicks the mouse at the last known mouse coordinates. + /// + void Click(); + + /// + /// Clicks and holds the mouse button at the last known mouse coordinates. + /// + void ClickAndHold(); + + /// + /// Releases the mouse button at the last known mouse coordinates. + /// + void Release(); + + /// + /// Right-clicks the mouse at the last known mouse coordinates. + /// + void ContextClick(); + + /// + /// Double-clicks the mouse at the last known mouse coordinates. + /// + void DoubleClick(); + + /// + /// Moves the mouse to the specified offset of the last known mouse coordinates. + /// + /// The horizontal offset to which to move the mouse. + /// The vertical offset to which to move the mouse. + void MoveByOffset(int offsetX, int offsetY); + + /// + /// Scrolls the current screen by specified offset. + /// + /// The horizontal offset relative to the view port. + /// The vertical offset relative to the view port. + void Scroll(int offsetX, int offsetY); + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/KeyboardActions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/KeyboardActions.cs new file mode 100644 index 0000000..c2ba474 --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/KeyboardActions.cs @@ -0,0 +1,42 @@ +using Aquality.Selenium.Core.Localization; +using OpenQA.Selenium.Appium.Windows; +using System; + +namespace Aquality.WinAppDriver.Actions +{ + /// + /// Implements Keyboard actions for the whole application. + /// + public class KeyboardActions : ApplicationActions, IKeyboardActions + { + public KeyboardActions(LocalizationLogger localizationLogger, Func> windowsDriverSupplier) + : base(localizationLogger, windowsDriverSupplier) + { + } + + public void PressKey(ModifierKey keyToPress) + { + LogAction("loc.keyboard.presskey", keyToPress); + PerformAction(actions => actions.KeyDown(keyToPress.GetKeysString())); + } + + public void ReleaseKey(ModifierKey keyToRelease) + { + LogAction("loc.keyboard.releasekey", keyToRelease); + PerformAction(actions => actions.KeyUp(keyToRelease.GetKeysString())); + } + + public void SendKeys(string keySequence) + { + LogAction("loc.keyboard.sendkeys", keySequence); + PerformAction(actions => actions.SendKeys(keySequence)); + } + + public void SendKeysWithKeyHold(string keySequence, ModifierKey keyToHold) + { + var keyToHoldString = keyToHold.GetKeysString(); + LogAction("loc.keyboard.sendkeys.withkeyhold", keySequence, keyToHold); + PerformAction(actions => actions.KeyDown(keyToHoldString).SendKeys(keySequence).KeyUp(keyToHoldString)); + } + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/ModifierKey.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/ModifierKey.cs new file mode 100644 index 0000000..e613f51 --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/ModifierKey.cs @@ -0,0 +1,56 @@ +using OpenQA.Selenium; +using System.Linq; +using System.Reflection; + +namespace Aquality.WinAppDriver.Actions +{ + /// + /// Represents modifier keys which could be used in . + /// Directly related to + /// + public enum ModifierKey + { + /// + /// Represents the Alt key. + /// + Alt, + /// + /// Represents the function key COMMAND. + /// + Command, + /// + /// Represents the Control key. + /// + Control, + /// + /// Represents the Left Alt key. + /// + LeftAlt, + /// + /// Represents the Left Control key. + /// + LeftControl, + /// + /// Represents the Left Shift key. + /// + LeftShift, + /// + /// Represents the function key META. + /// + Meta, + /// + /// Represents the Shift key. + /// + Shift + } + + internal static class ModifierKeyExtensions + { + public static string GetKeysString(this ModifierKey modifierKey) + { + return typeof(Keys) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(field => field.Name == modifierKey.ToString())?.GetValue(null).ToString(); + } + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/MouseActions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/MouseActions.cs new file mode 100644 index 0000000..9aaef02 --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Actions/MouseActions.cs @@ -0,0 +1,63 @@ +using Aquality.Selenium.Core.Localization; +using OpenQA.Selenium.Appium.Windows; +using OpenQA.Selenium.Remote; +using System; + +namespace Aquality.WinAppDriver.Actions +{ + /// + /// Implements Mouse actions for the whole application. + /// + public class MouseActions : ApplicationActions, IMouseActions + { + private readonly Func remoteTouchScreenSupplier; + + public MouseActions(LocalizationLogger localizationLogger, Func> windowsDriverSupplier) + : base(localizationLogger, windowsDriverSupplier) + { + remoteTouchScreenSupplier = () => new RemoteTouchScreen(windowsDriverSupplier()); + } + + public void Click() + { + LogAction("loc.mouse.click"); + PerformAction(actions => actions.Click()); + } + + public void ClickAndHold() + { + LogAction("loc.mouse.clickandhold"); + PerformAction(actions => actions.ClickAndHold()); + } + + public void Release() + { + LogAction("loc.mouse.release"); + PerformAction(actions => actions.Release()); + } + + public void ContextClick() + { + LogAction("loc.mouse.contextclick"); + PerformAction(actions => actions.ContextClick()); + } + + public void DoubleClick() + { + LogAction("loc.mouse.doubleclick"); + PerformAction(actions => actions.DoubleClick()); + } + + public void MoveByOffset(int offsetX, int offsetY) + { + LogAction("loc.mouse.movebyoffset", offsetX, offsetY); + PerformAction(actions => actions.MoveByOffset(offsetX, offsetY)); + } + + public void Scroll(int offsetX, int offsetY) + { + LogAction("loc.mouse.scrollbyoffset", offsetX, offsetY); + remoteTouchScreenSupplier().Scroll(offsetX, offsetY); + } + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/Application.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/Application.cs index 89d8adb..4102c0c 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/Application.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/Application.cs @@ -2,6 +2,8 @@ using Aquality.Selenium.Core.Applications; using Aquality.Selenium.Core.Configurations; using Aquality.Selenium.Core.Localization; +using Aquality.WinAppDriver.Actions; +using Microsoft.Extensions.DependencyInjection; using OpenQA.Selenium.Appium.Windows; using OpenQA.Selenium.Remote; @@ -13,19 +15,21 @@ namespace Aquality.WinAppDriver.Applications public class Application : IApplication { private TimeSpan implicitWait; - + /// /// Instantiate application. /// /// Instance of WinAppDriver - /// Instance of - /// Instance of - public Application(WindowsDriver windowsDriver, ITimeoutConfiguration timeoutConfiguration, LocalizationLogger logger) + /// Service provider to resolve all dependencies from DI container + public Application(WindowsDriver windowsDriver, IServiceProvider serviceProvider) { - Logger = logger; WindowsDriver = windowsDriver; + Logger = serviceProvider.GetRequiredService(); + KeyboardActions = serviceProvider.GetRequiredService(); + MouseActions = serviceProvider.GetRequiredService(); + var timeoutConfiguration = serviceProvider.GetRequiredService(); WindowsDriver.Manage().Timeouts().ImplicitWait = timeoutConfiguration.Implicit; - logger.Info("loc.application.ready"); + Logger.Info("loc.application.ready"); } private LocalizationLogger Logger { get; } @@ -37,6 +41,10 @@ public Application(WindowsDriver windowsDriver, ITimeoutConfigur /// public WindowsDriver WindowsDriver { get; } + public IKeyboardActions KeyboardActions { get; } + + public IMouseActions MouseActions { get; } + /// /// Sets WinAppDriver ImplicitWait timeout. /// Default value: . diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/ApplicationFactory.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/ApplicationFactory.cs index b0e634a..7a647be 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/ApplicationFactory.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/ApplicationFactory.cs @@ -1,5 +1,7 @@ -using Aquality.Selenium.Core.Localization; -using OpenQA.Selenium.Appium; +using Aquality.Selenium.Core.Configurations; +using Aquality.Selenium.Core.Localization; +using Aquality.WinAppDriver.Configurations; +using Microsoft.Extensions.DependencyInjection; using OpenQA.Selenium.Appium.Windows; using System; @@ -7,20 +9,28 @@ namespace Aquality.WinAppDriver.Applications { public abstract class ApplicationFactory : IApplicationFactory { - private readonly LocalizationLogger localizationLogger; + private readonly IDriverSettings driverSettings; + private readonly ITimeoutConfiguration timeoutConfiguration; - protected ApplicationFactory(LocalizationLogger localizationLogger) + protected LocalizationLogger LocalizationLogger { get; } + protected IServiceProvider ServiceProvider { get; } + + protected ApplicationFactory(IServiceProvider serviceProvider) { - this.localizationLogger = localizationLogger; + LocalizationLogger = serviceProvider.GetRequiredService(); + driverSettings = serviceProvider.GetRequiredService(); + timeoutConfiguration = serviceProvider.GetRequiredService(); + ServiceProvider = serviceProvider; } public abstract Application Application { get; } - protected WindowsDriver GetDriver(Uri driverServerUri, AppiumOptions options, TimeSpan commandTimeout) + protected WindowsDriver GetDriver(Uri driverServerUri) { + var options = driverSettings.AppiumOptions; options.ToDictionary().TryGetValue("app", out var appPath); - localizationLogger.Info("loc.application.start", appPath); - return new WindowsDriver(driverServerUri, options, commandTimeout); + LocalizationLogger.Info("loc.application.start", appPath); + return new WindowsDriver(driverServerUri, options, timeoutConfiguration.Command); } } } diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/ApplicationManager.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/ApplicationManager.cs index 7d8846d..3e818a6 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/ApplicationManager.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/ApplicationManager.cs @@ -11,6 +11,7 @@ using OpenQA.Selenium.Appium.Service; using Aquality.Selenium.Core.Logging; using System.Reflection; +using Aquality.WinAppDriver.Actions; namespace Aquality.WinAppDriver.Applications { @@ -41,18 +42,57 @@ public static bool TryToStopAppiumLocalService() /// /// Provides current instance of application /// - public static Application Application => GetApplication(StartApplicationFunction, () => RegisterServices(services => Application)); + public static Application Application + { + get + { + return GetApplication(StartApplicationFunction, () => RegisterServices(services => Application)); + } + set + { + SetApplication(value); + } + } /// /// Provides access to Aquality services, registered in DI container. /// - public static IServiceProvider ServiceProvider => GetServiceProvider(services => Application, () => RegisterServices(services => Application)); + public static IServiceProvider ServiceProvider + { + get + { + return GetServiceProvider(services => Application, () => RegisterServices(services => Application)); + } + set + { + SetServiceProvider(value); + } + } + + /// + /// Factory for application creation. + /// + public static IApplicationFactory ApplicationFactory + { + get + { + if (!ApplicationFactoryContainer.IsValueCreated) + { + SetDefaultFactory(); + } + return ApplicationFactoryContainer.Value; + } + set + { + ApplicationFactoryContainer.Value = value; + } + } /// /// Resolves required service from /// /// type of required service - /// . + /// Thrown if there is no service of the required type. /// public static T GetRequiredService() { @@ -66,30 +106,17 @@ public static T GetRequiredService() public static void SetDefaultFactory() { var appProfile = GetRequiredService(); - var driverSettings = GetRequiredService(); - var localizationLogger = GetRequiredService(); - var timeoutConfiguration = GetRequiredService(); - IApplicationFactory applicationFactory; if (appProfile.IsRemote) { - applicationFactory = new RemoteApplicationFactory(appProfile.RemoteConnectionUrl, driverSettings, timeoutConfiguration, localizationLogger); + applicationFactory = new RemoteApplicationFactory(appProfile.RemoteConnectionUrl, ServiceProvider); } else { - applicationFactory = new LocalApplicationFactory(AppiumLocalServiceContainer.Value, driverSettings, timeoutConfiguration, localizationLogger); + applicationFactory = new LocalApplicationFactory(AppiumLocalServiceContainer.Value, ServiceProvider); } - SetFactory(applicationFactory); - } - - /// - /// Sets custom application factory. - /// - /// Custom implementation of - public static void SetFactory(IApplicationFactory applicationFactory) - { - ApplicationFactoryContainer.Value = applicationFactory; + ApplicationFactory = applicationFactory; } private static IServiceCollection RegisterServices(Func applicationSupplier) @@ -100,10 +127,12 @@ private static IServiceCollection RegisterServices(Func(); services.AddTransient(); - var driverSettings = new DriverSettings(settingsFile); - services.AddSingleton(driverSettings); - services.AddSingleton(new ApplicationProfile(settingsFile, driverSettings)); - services.AddSingleton(new LocalizationManager(new LoggerConfiguration(settingsFile), Logger.Instance, Assembly.GetExecutingAssembly())); + services.AddSingleton(serviceProvider => new DriverSettings(settingsFile)); + services.AddSingleton(serviceProvider => new ApplicationProfile(settingsFile, serviceProvider.GetRequiredService())); + services.AddSingleton(serviceProvider => new LocalizationManager(serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), Assembly.GetExecutingAssembly())); + services.AddSingleton(serviceProvider => new KeyboardActions(serviceProvider.GetRequiredService(), () => Application.WindowsDriver)); + services.AddSingleton(serviceProvider => new MouseActions(serviceProvider.GetRequiredService(), () => Application.WindowsDriver)); + services.AddSingleton(serviceProvider => ApplicationFactory); return services; } @@ -111,11 +140,7 @@ private static Func StartApplicationFunction { get { - if (!ApplicationFactoryContainer.IsValueCreated) - { - SetDefaultFactory(); - } - return (services) => ApplicationFactoryContainer.Value.Application; + return (services) => ApplicationFactory.Application; } } } diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/LocalApplicationFactory.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/LocalApplicationFactory.cs index cc4cff7..0f528f7 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/LocalApplicationFactory.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/LocalApplicationFactory.cs @@ -1,23 +1,17 @@ -using Aquality.Selenium.Core.Configurations; -using Aquality.Selenium.Core.Localization; -using Aquality.WinAppDriver.Configurations; +using Aquality.Selenium.Core.Localization; using OpenQA.Selenium.Appium.Service; +using System; namespace Aquality.WinAppDriver.Applications { public class LocalApplicationFactory : ApplicationFactory { private readonly AppiumLocalService driverService; - private readonly IDriverSettings driverSettings; - private readonly ITimeoutConfiguration timeoutConfiguration; - private readonly LocalizationLogger localizationLogger; - public LocalApplicationFactory(AppiumLocalService driverService, IDriverSettings driverSettings, ITimeoutConfiguration timeoutConfiguration, LocalizationLogger localizationLogger) : base(localizationLogger) + public LocalApplicationFactory(AppiumLocalService driverService, IServiceProvider serviceProvider) + : base(serviceProvider) { this.driverService = driverService; - this.driverSettings = driverSettings; - this.timeoutConfiguration = timeoutConfiguration; - this.localizationLogger = localizationLogger; } public override Application Application @@ -26,9 +20,9 @@ public override Application Application { driverService.Start(); var serviceUrl = driverService.ServiceUrl; - localizationLogger.Info("loc.application.driver.service.local.start", serviceUrl); - var driver = GetDriver(serviceUrl, driverSettings.AppiumOptions, timeoutConfiguration.Command); - return new Application(driver, timeoutConfiguration, localizationLogger); + LocalizationLogger.Info("loc.application.driver.service.local.start", serviceUrl); + var driver = GetDriver(serviceUrl); + return new Application(driver, ServiceProvider); } } } diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/RemoteApplicationFactory.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/RemoteApplicationFactory.cs index 6bed05e..2622205 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/RemoteApplicationFactory.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Applications/RemoteApplicationFactory.cs @@ -1,6 +1,4 @@ -using Aquality.Selenium.Core.Configurations; -using Aquality.Selenium.Core.Localization; -using Aquality.WinAppDriver.Configurations; +using Aquality.Selenium.Core.Localization; using OpenQA.Selenium.Remote; using System; @@ -9,26 +7,21 @@ namespace Aquality.WinAppDriver.Applications public class RemoteApplicationFactory : ApplicationFactory { private readonly Uri driverServerUri; - private readonly IDriverSettings driverSettings; - private readonly ITimeoutConfiguration timeoutConfiguration; - private readonly LocalizationLogger localizationLogger; - public RemoteApplicationFactory(Uri driverServerUri, IDriverSettings driverSettings, ITimeoutConfiguration timeoutConfiguration, LocalizationLogger localizationLogger) : base(localizationLogger) + public RemoteApplicationFactory(Uri driverServerUri, IServiceProvider serviceProvider) + : base(serviceProvider) { this.driverServerUri = driverServerUri; - this.driverSettings = driverSettings; - this.timeoutConfiguration = timeoutConfiguration; - this.localizationLogger = localizationLogger; } public override Application Application { get { - localizationLogger.Info("loc.application.driver.service.remote", driverServerUri); - var driver = GetDriver(driverServerUri, driverSettings.AppiumOptions, timeoutConfiguration.Command); + LocalizationLogger.Info("loc.application.driver.service.remote", driverServerUri); + var driver = GetDriver(driverServerUri); driver.FileDetector = new LocalFileDetector(); - return new Application(driver, timeoutConfiguration, localizationLogger); + return new Application(driver, ServiceProvider); } } } diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.csproj b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.csproj index 0c59001..4508ec3 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.csproj +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.csproj @@ -18,6 +18,11 @@ true + + Aquality.WinAppDriver.xml + 1701;1702;1591 + + @@ -41,7 +46,7 @@ - + diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.xml b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.xml new file mode 100644 index 0000000..b09b64a --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.xml @@ -0,0 +1,571 @@ + + + + Aquality.WinAppDriver + + + + + Abstract class for any actions against the whole application. + + + + + Instantiates Aplication actions. + + Logger for localized values. + Method to get current application session. + + + + Performs submitted action against new object. + + Action to be performed. + + + + Logs keyboard action in specific format. + + Key of the localized message. + Arguments for the localized message. + + + + Provides methods representing basic keyboard actions. + + + + + Presses a key. + + The value representing the key to press. + + + + Releases a key. + + The value representing the key to release. + + + + Sends a sequence of keystrokes to the target. + + A string representing the keystrokes to send. + + + + Sends a sequence of keystrokes to the application, holding a specified key. + After the action, holded key is released. + + A string representing the keystrokes to send. + The value representing the key to hold. + + + + Provides methods representing basic mouse actions. + + + + + Clicks the mouse at the last known mouse coordinates. + + + + + Clicks and holds the mouse button at the last known mouse coordinates. + + + + + Releases the mouse button at the last known mouse coordinates. + + + + + Right-clicks the mouse at the last known mouse coordinates. + + + + + Double-clicks the mouse at the last known mouse coordinates. + + + + + Moves the mouse to the specified offset of the last known mouse coordinates. + + The horizontal offset to which to move the mouse. + The vertical offset to which to move the mouse. + + + + Scrolls the current screen by specified offset. + + The horizontal offset relative to the view port. + The vertical offset relative to the view port. + + + + Implements Keyboard actions for the whole application. + + + + + Represents modifier keys which could be used in . + Directly related to + + + + + Represents the Alt key. + + + + + Represents the function key COMMAND. + + + + + Represents the Control key. + + + + + Represents the Left Alt key. + + + + + Represents the Left Control key. + + + + + Represents the Left Shift key. + + + + + Represents the function key META. + + + + + Represents the Shift key. + + + + + Implements Mouse actions for the whole application. + + + + + Provides functionality to work with Windows application via WinAppDriver. + + + + + Instantiate application. + + Instance of WinAppDriver + Service provider to resolve all dependencies from DI container + + + + Provides instance of Windows Driver + + + + + Sets WinAppDriver ImplicitWait timeout. + Default value: . + + Desired Implicit wait timeout. + + + + Quit application. + + + + + Controls application and Aquality services + + + + + Stops appium local service. + + True if service was running, false otherwise + + + + Provides current instance of application + + + + + Provides access to Aquality services, registered in DI container. + + + + + Factory for application creation. + + + + + Resolves required service from + + type of required service + Thrown if there is no service of the required type. + + + + + Sets default factory responsible for application creation. + RemoteApplicationFactory if value set in configuration and LocalApplicationFactory otherwise. + + + + + Provides application profile. + + + + + Instantiates class using JSON file with general settings. + + JSON settings file. + Instance of + + + + Provides target application profile. + + + + + Instantiates class using JSON file with general settings. + + JSON settings file. + + + + Defines does the current settings have the application path defined + + + + + Describes application settings. + + + + + Is remote WinAppDriver service or not: true to use and false to create default . + + + + + Gets remote connection URI is case of remote browser. + + + + + Gets WinAppDriver settings for application. + + + + + Describes WinAppDriver settings. + + + + + Gets desired WinAppDriver options + + + + + Provides a path to the application + + + + + Abstract class for any actions agains element. + + + + + Instantiates Element actions for a specific element. + + Target element. + Target element's type. + Method to get current application session. + Logger for localized values. + Retrier for element actions. + + + + Performs submitted action against new object. + + Action to be performed. + + + + Logs element action in specific format. + + Key of the localized message. + Arguments for the localized message. + + + + Provides methods representing basic mouse actions against the current element. + Current element's coordinates are user as last known mouse coordinates. + + + + + Performs a drag-and-drop operation on the current element; drops it on target element. + + The element on which the drop is performed. + + + + Performs a drag-and-drop operation on the current element to a specified offset. + + The horizontal offset to which to move the mouse. + The vertical offset to which to move the mouse. + + + + Moves the current element to a specified offset. Works the same as to + + The horizontal offset to which to move the mouse. + The vertical offset to which to move the mouse. + + + + Moves the mouse from the current element. + + + + + Moves the mouse to the current element. + + + + + Moves the mouse to the specified offset of the top-left corner of the current element. + + The horizontal offset to which to move the mouse. + The vertical offset to which to move the mouse. + + + + Moves the mouse to the specified offset of specified offset origin of the current element. + + The horizontal offset to which to move the mouse. + The vertical offset to which to move the mouse. + The value from which to calculate the offset. + + + + Scrolls the current screen by specified offset, starting from the current element. + + + + + + + Implements Keyboard actions for a specific element. + + + + + Instantiates Keyboard actions for a specific element. + + Target element. + Target element's type. + Method to get current application session. + Logger for localized values. + Retrier for element actions. + + + + Implements Mouse actions for a specific element. + + + + + Instantiates Mouse actions for a specific element. + + Target element. + Target element's type. + Method to get current application session. + Logger for localized values. + Retrier for element actions. + + + + Defines Button UI element. + + + + + Factory that creates elements. + + + + + Describes behavior of Button UI element. + + + + + Descibes behavior of any application element. + + + + + Provides access to against the current element. + + + + + Provides access to against the current element. + + + + + Defines the interface used to create the windows application's elements. + + + + + Creates element that implements IButton interface. + + Element locator + Element name + Instance of element that implements IButton interface + + + + Creates element that implements ILabel interface. + + Element locator + Element name + Instance of element that implements ILabel interface + + + + Creates element that implements ITextBox interface. + + Element locator + Element name + Instance of element that implements ITextBox interface + + + + Describes behavior of Label UI element. + + + + + Describes behavior of TextBox UI element. + + + + + Gets text value of an element. + + String representation of element's value + + + + Type text in an element. + + Text to type. + Should the typing text be hidden in logs or not. False by default. + + + + Clear element text and type value. + + Text to type. + Should the typing text be hidden in logs or not. False by default. + + + + Defines Label UI element. + + + + + Defines TextBox UI element. + + + + + Provides extension methods for classes. + + + + + element.GetType().Name provides a non-localized name; + ElementType property is localized, but has the protected access and is not defined in the IElement interface. + So current method allows to get ElementType if the current element is assignable from + + + + + + + Defines base class for any application's window. + + + + + Constructor with parameters. + + Unique locator of the window. + Name of the window. + + + + Locator of specified window. + + + + + Name of specified window. + + + + + Instance of logger + + Logger instance. + + + + Element factory + + Element factory. + + + + Return window state for window locator + + True - window is opened, + False - window is not opened. + + + + Gets size of window element defined by its locator. + + + + diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Configurations/ApplicationProfile.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Configurations/ApplicationProfile.cs index 19098ca..9bd75cc 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Configurations/ApplicationProfile.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Configurations/ApplicationProfile.cs @@ -12,6 +12,7 @@ public class ApplicationProfile : IApplicationProfile /// Instantiates class using JSON file with general settings. /// /// JSON settings file. + /// Instance of public ApplicationProfile(JsonFile settingsFile, IDriverSettings driverSettings) { SettingsFile = settingsFile; diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/ElementActions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/ElementActions.cs new file mode 100644 index 0000000..d72d988 --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/ElementActions.cs @@ -0,0 +1,61 @@ +using Aquality.Selenium.Core.Applications; +using Aquality.Selenium.Core.Localization; +using Aquality.Selenium.Core.Utilities; +using Aquality.WinAppDriver.Elements.Interfaces; +using OpenQA.Selenium; +using System; +using SeleniumActions = OpenQA.Selenium.Interactions.Actions; + +namespace Aquality.WinAppDriver.Elements.Actions +{ + /// + /// Abstract class for any actions agains element. + /// + public abstract class ElementActions + { + private readonly IElement element; + private readonly string elementType; + private readonly Func applicationSupplier; + private readonly LocalizationLogger localizationLogger; + private readonly ElementActionRetrier elementActionsRetrier; + + /// + /// Instantiates Element actions for a specific element. + /// + /// Target element. + /// Target element's type. + /// Method to get current application session. + /// Logger for localized values. + /// Retrier for element actions. + protected ElementActions(IElement element, string elementType, Func applicationSupplier, LocalizationLogger localizationLogger, ElementActionRetrier elementActionsRetrier) + { + this.element = element; + this.elementType = elementType; + this.applicationSupplier = applicationSupplier; + this.localizationLogger = localizationLogger; + this.elementActionsRetrier = elementActionsRetrier; + } + + /// + /// Performs submitted action against new object. + /// + /// Action to be performed. + protected virtual void PerformAction(Func action) + { + elementActionsRetrier.DoWithRetry(() => + { + action(new SeleniumActions(applicationSupplier().Driver), element.GetElement()).Build().Perform(); + }); + } + + /// + /// Logs element action in specific format. + /// + /// Key of the localized message. + /// Arguments for the localized message. + protected virtual void LogAction(string messageKey, params object[] args) + { + localizationLogger.InfoElementAction(elementType, element.Name, messageKey, args); + } + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/IMouseActions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/IMouseActions.cs new file mode 100644 index 0000000..5f9db93 --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/IMouseActions.cs @@ -0,0 +1,64 @@ +using Aquality.WinAppDriver.Elements.Interfaces; +using OpenQA.Selenium.Interactions; + +namespace Aquality.WinAppDriver.Elements.Actions +{ + /// + /// Provides methods representing basic mouse actions against the current element. + /// Current element's coordinates are user as last known mouse coordinates. + /// + public interface IMouseActions : WinAppDriver.Actions.IMouseActions + { + /// + /// Performs a drag-and-drop operation on the current element; drops it on target element. + /// + /// The element on which the drop is performed. + void DragAndDrop(IElement target); + + /// + /// Performs a drag-and-drop operation on the current element to a specified offset. + /// + /// The horizontal offset to which to move the mouse. + /// The vertical offset to which to move the mouse. + void DragAndDropToOffset(int offsetX, int offsetY); + + /// + /// Moves the current element to a specified offset. Works the same as to + /// + /// The horizontal offset to which to move the mouse. + /// The vertical offset to which to move the mouse. + new void MoveByOffset(int offsetX, int offsetY); + + /// + /// Moves the mouse from the current element. + /// + void MoveFromElement(); + + /// + /// Moves the mouse to the current element. + /// + void MoveToElement(); + + /// + /// Moves the mouse to the specified offset of the top-left corner of the current element. + /// + /// The horizontal offset to which to move the mouse. + /// The vertical offset to which to move the mouse. + void MoveToElement(int offsetX, int offsetY); + + /// + /// Moves the mouse to the specified offset of specified offset origin of the current element. + /// + /// The horizontal offset to which to move the mouse. + /// The vertical offset to which to move the mouse. + /// The value from which to calculate the offset. + void MoveToElement(int offsetX, int offsetY, MoveToElementOffsetOrigin offsetOrigin); + + /// + /// Scrolls the current screen by specified offset, starting from the current element. + /// + /// + /// + new void Scroll(int offsetX, int offsetY); + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/KeyboardActions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/KeyboardActions.cs new file mode 100644 index 0000000..922bd0f --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/KeyboardActions.cs @@ -0,0 +1,53 @@ +using Aquality.Selenium.Core.Applications; +using Aquality.Selenium.Core.Localization; +using Aquality.Selenium.Core.Utilities; +using Aquality.WinAppDriver.Actions; +using Aquality.WinAppDriver.Elements.Interfaces; +using System; + +namespace Aquality.WinAppDriver.Elements.Actions +{ + /// + /// Implements Keyboard actions for a specific element. + /// + public class KeyboardActions : ElementActions, IKeyboardActions + { + /// + /// Instantiates Keyboard actions for a specific element. + /// + /// Target element. + /// Target element's type. + /// Method to get current application session. + /// Logger for localized values. + /// Retrier for element actions. + public KeyboardActions(IElement element, string elementType, Func applicationSupplier, LocalizationLogger localizationLogger, ElementActionRetrier elementActionsRetrier) + : base(element, elementType, applicationSupplier, localizationLogger, elementActionsRetrier) + { + } + + public void PressKey(ModifierKey keyToPress) + { + LogAction("loc.keyboard.presskey", keyToPress); + PerformAction((actions, element) => actions.KeyDown(element, keyToPress.GetKeysString())); + } + + public void ReleaseKey(ModifierKey keyToRelease) + { + LogAction("loc.keyboard.releasekey", keyToRelease); + PerformAction((actions, element) => actions.KeyUp(element, keyToRelease.GetKeysString())); + } + + public void SendKeys(string keySequence) + { + LogAction("loc.keyboard.sendkeys", keySequence); + PerformAction((actions, element) => actions.SendKeys(element, keySequence)); + } + + public void SendKeysWithKeyHold(string keySequence, ModifierKey keyToHold) + { + var keyToHoldString = keyToHold.GetKeysString(); + LogAction("loc.keyboard.sendkeys.withkeyhold", keySequence, keyToHold); + PerformAction((actions, element) => actions.KeyDown(element, keyToHoldString).SendKeys(element, keySequence).KeyUp(element, keyToHoldString)); + } + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/MouseActions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/MouseActions.cs new file mode 100644 index 0000000..5c4cf57 --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Actions/MouseActions.cs @@ -0,0 +1,114 @@ +using Aquality.Selenium.Core.Applications; +using Aquality.Selenium.Core.Localization; +using Aquality.Selenium.Core.Utilities; +using Aquality.WinAppDriver.Elements.Interfaces; +using Aquality.WinAppDriver.Extensions; +using OpenQA.Selenium.Interactions; +using OpenQA.Selenium.Remote; +using System; + +namespace Aquality.WinAppDriver.Elements.Actions +{ + /// + /// Implements Mouse actions for a specific element. + /// + public class MouseActions : ElementActions, IMouseActions + { + private readonly IElement element; + private readonly Func remoteTouchScreenSupplier; + + /// + /// Instantiates Mouse actions for a specific element. + /// + /// Target element. + /// Target element's type. + /// Method to get current application session. + /// Logger for localized values. + /// Retrier for element actions. + public MouseActions(IElement element, string elementType, Func applicationSupplier, LocalizationLogger localizationLogger, ElementActionRetrier elementActionsRetrier) + : base(element, elementType, applicationSupplier, localizationLogger, elementActionsRetrier) + { + this.element = element; + remoteTouchScreenSupplier = () => new RemoteTouchScreen(applicationSupplier().Driver); + } + + public void Click() + { + LogAction("loc.mouse.click"); + PerformAction((actions, element) => actions.Click(element)); + } + + public void ClickAndHold() + { + LogAction("loc.mouse.clickandhold"); + PerformAction((actions, element) => actions.ClickAndHold(element)); + } + + public void Release() + { + LogAction("loc.mouse.release"); + PerformAction((actions, element) => actions.Release(element)); + } + + public void ContextClick() + { + LogAction("loc.mouse.contextclick"); + PerformAction((actions, element) => actions.ContextClick(element)); + } + + public void DoubleClick() + { + LogAction("loc.mouse.doubleclick"); + PerformAction((actions, element) => actions.DoubleClick(element)); + } + + public void MoveByOffset(int offsetX, int offsetY) + { + DragAndDropToOffset(offsetX, offsetY); + } + + public void DragAndDrop(IElement target) + { + LogAction("loc.mouse.draganddrop", target.GetElementType(), target.Name); + PerformAction((actions, element) => actions.DragAndDrop(element, target.GetElement())); + } + + public void DragAndDropToOffset(int offsetX, int offsetY) + { + LogAction("loc.mouse.draganddrop.tooffset", offsetX, offsetY); + PerformAction((actions, element) => actions.DragAndDropToOffset(element, offsetX, offsetY)); + } + + public void MoveFromElement() + { + var offsetX = - element.GetElement().Size.Width / 2; + var offsetY = - element.GetElement().Size.Height / 2; + LogAction("loc.mouse.movefromelement", offsetX, offsetY); + PerformAction((actions, element) => actions.MoveToElement(element, offsetX, offsetY)); + } + + public void MoveToElement() + { + LogAction("loc.mouse.movetoelement"); + PerformAction((actions, element) => actions.MoveToElement(element)); + } + + public void MoveToElement(int offsetX, int offsetY) + { + LogAction("loc.mouse.movetoelement.byoffset", offsetX, offsetY); + PerformAction((actions, element) => actions.MoveToElement(element, offsetX, offsetY)); + } + + public void MoveToElement(int offsetX, int offsetY, MoveToElementOffsetOrigin offsetOrigin) + { + LogAction("loc.mouse.movetoelement.byoffset.withorigin", offsetX, offsetY, offsetOrigin); + PerformAction((actions, element) => actions.MoveToElement(element, offsetX, offsetY, offsetOrigin)); + } + + public void Scroll(int offsetX, int offsetY) + { + LogAction("loc.mouse.scrollbyoffset", offsetX, offsetY); + remoteTouchScreenSupplier().Scroll(element.GetElement().Coordinates, offsetX, offsetY); + } + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Element.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Element.cs index 801ce0f..dcb7241 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Element.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Element.cs @@ -4,15 +4,17 @@ using Aquality.Selenium.Core.Utilities; using Aquality.Selenium.Core.Waitings; using Aquality.WinAppDriver.Applications; +using Aquality.WinAppDriver.Elements.Actions; using Aquality.WinAppDriver.Elements.Interfaces; using OpenQA.Selenium; +using IKeyboardActions = Aquality.WinAppDriver.Actions.IKeyboardActions; using CoreElement = Aquality.Selenium.Core.Elements.Element; using CoreElementFactory = Aquality.Selenium.Core.Elements.Interfaces.IElementFactory; using CoreElementFinder = Aquality.Selenium.Core.Elements.Interfaces.IElementFinder; namespace Aquality.WinAppDriver.Elements { - public abstract class Element : CoreElement + public abstract class Element : CoreElement, IElement { protected Element(By locator, string name) : base(locator, name, ElementState.Displayed) { @@ -26,7 +28,11 @@ protected Element(By locator, string name) : base(locator, name, ElementState.Di protected override CoreElementFactory Factory => CustomFactory; - protected IElementFactory CustomFactory => ApplicationManager.GetRequiredService(); + protected virtual IElementFactory CustomFactory => ApplicationManager.GetRequiredService(); + + public virtual IKeyboardActions KeyboardActions => new KeyboardActions(this, ElementType, () => Application, LocalizationLogger, ActionRetrier); + + public virtual IMouseActions MouseActions => new MouseActions(this, ElementType, () => Application, LocalizationLogger, ActionRetrier); public T FindChildElement(By childLocator, ElementSupplier supplier = null) where T : IElement { diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElement.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElement.cs index f059d7b..9416781 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElement.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElement.cs @@ -1,8 +1,22 @@ -using CoreElement = Aquality.Selenium.Core.Elements.Interfaces.IElement; +using Aquality.WinAppDriver.Elements.Actions; +using CoreElement = Aquality.Selenium.Core.Elements.Interfaces.IElement; +using IKeyboardActions = Aquality.WinAppDriver.Actions.IKeyboardActions; namespace Aquality.WinAppDriver.Elements.Interfaces { + /// + /// Descibes behavior of any application element. + /// public interface IElement : CoreElement { + /// + /// Provides access to against the current element. + /// + IKeyboardActions KeyboardActions { get; } + + /// + /// Provides access to against the current element. + /// + IMouseActions MouseActions { get; } } } diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElementFactory.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElementFactory.cs index dc651fd..57407dd 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElementFactory.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElementFactory.cs @@ -21,7 +21,6 @@ public interface IElementFactory : CoreElementFactory /// /// Element locator /// Element name - /// Element state /// Instance of element that implements ILabel interface ILabel GetLabel(By locator, string name); diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Extensions/ElementExtensions.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Extensions/ElementExtensions.cs new file mode 100644 index 0000000..abe7a89 --- /dev/null +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Extensions/ElementExtensions.cs @@ -0,0 +1,30 @@ +using Aquality.Selenium.Core.Elements; +using Aquality.WinAppDriver.Elements.Interfaces; +using System.Reflection; + +namespace Aquality.WinAppDriver.Extensions +{ + /// + /// Provides extension methods for classes. + /// + internal static class ElementExtensions + { + /// + /// element.GetType().Name provides a non-localized name; + /// ElementType property is localized, but has the protected access and is not defined in the IElement interface. + /// So current method allows to get ElementType if the current element is assignable from + /// + /// + /// + internal static string GetElementType(this IElement element) + { + string elementType = null; + if (typeof(Element).IsAssignableFrom(element.GetType())) + { + elementType = element.GetType().GetProperty("ElementType", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(element).ToString(); + } + + return elementType ?? element.GetType().Name; + } + } +} diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/be.json b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/be.json index 1cfb140..a7b06d4 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/be.json +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/be.json @@ -17,5 +17,22 @@ "loc.label": "Надпіс", "loc.text.field": "Тэкставае поле", "loc.text.clearing": "Aчышчаем", - "loc.text.typing": "Уводзім значэнне '{0}'" + "loc.text.typing": "Уводзім значэнне '{0}'", + "loc.keyboard.presskey": "Заціскаем клавішу '{0}'", + "loc.keyboard.releasekey": "Адпускаем клавішу '{0}'", + "loc.keyboard.sendkeys": "Націскаем клавішы '{0}'", + "loc.keyboard.sendkeys.withkeyhold": "Націскаем клавішы '{0}' з заціснутай клавішай '{1}'", + "loc.mouse.click": "Націскаем кнопку мышы", + "loc.mouse.clickandhold": "Заціскаем кнопку мышы", + "loc.mouse.release": "Адпускаем кнопку мышы", + "loc.mouse.contextclick": "Націскаем правую кнопку мышы", + "loc.mouse.doubleclick": "Падвойна націскаем кнопку мышы", + "loc.mouse.movebyoffset": "Перамяшчаем курсор мышы на '{0}' пунктаў па вертыкалі і '{1}' пунктаў па гарызанталі", + "loc.mouse.draganddrop": "Перамяшчаем курсор мышы да элемента '{0} '{1}''", + "loc.mouse.draganddrop.tooffset": "Перамяшчаем курсор мышы на '{0}' пунктаў па вертыкалі і '{1}' пунктаў па гарызанталі", + "loc.mouse.movefromelement": "Адводзім курсор мышы ад элемента", + "loc.mouse.movetoelement": "Наводзім курсор мышы на элемент", + "loc.mouse.movetoelement.byoffset": "Наводзім курсор мышы на элемент са змяшчэннем на '{0}' пунктаў па вертыкалі і '{1}' пунктаў па гарызанталі", + "loc.mouse.movetoelement.byoffset.withorigin": "Наводзім курсор мышы на элемент са змяшчэннем на '{0}' пунктаў па вертыкалі і '{1}' пунктаў па гарызанталі, пачынаючы ад '{2}'", + "loc.mouse.scrollbyoffset": "Пракручваем кольца мышы на '{0}' пунктаў па вертыкалі і '{1}' пунктаў па гарызанталі" } \ No newline at end of file diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/en.json b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/en.json index 67e7054..84bac09 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/en.json +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/en.json @@ -17,5 +17,22 @@ "loc.label": "Label", "loc.text.field": "Text Field", "loc.text.clearing": "Clearing", - "loc.text.typing": "Typing '{0}'" + "loc.text.typing": "Typing '{0}'", + "loc.keyboard.presskey": "Pressing key '{0}'", + "loc.keyboard.releasekey": "Releasing key '{0}'", + "loc.keyboard.sendkeys": "Sending keys '{0}'", + "loc.keyboard.sendkeys.withkeyhold": "Sending keys '{0}' with holded key '{1}'", + "loc.mouse.click": "Clicking mouse button", + "loc.mouse.clickandhold": "Holding mouse button", + "loc.mouse.release": "Releasing mouse button", + "loc.mouse.contextclick": "Clicking right mouse button", + "loc.mouse.doubleclick": "Double-clicking mouse button", + "loc.mouse.movebyoffset": "Moving mouse by '{0}' vertical and '{1}' horizontal offset", + "loc.mouse.draganddrop": "Drag-and-drop to element '{0} '{1}''", + "loc.mouse.draganddrop.tooffset": "Drag-and-drop to '{0}' vertical and '{1}' horizontal offset", + "loc.mouse.movefromelement": "Moving mouse from element", + "loc.mouse.movetoelement": "Moving mouse to element", + "loc.mouse.movetoelement.byoffset": "Moving mouse to element by '{0}' vertical and '{1}' horizontal offset", + "loc.mouse.movetoelement.byoffset.withorigin": "Moving mouse to element by '{0}' vertical and '{1}' horizontal offset with origin at '{2}'", + "loc.mouse.scrollbyoffset": "Scroll mouse wheel by '{0}' vertical and '{1}' horizontal offset" } \ No newline at end of file diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/ru.json b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/ru.json index 835bca8..ec379db 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/ru.json +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/Localization/ru.json @@ -17,5 +17,22 @@ "loc.label": "Надпись", "loc.text.field": "Текстовое поле", "loc.text.clearing": "Очищаем", - "loc.text.typing": "Вводим значение '{0}'" + "loc.text.typing": "Вводим значение '{0}'", + "loc.keyboard.presskey": "Зажимаем клавишу '{0}'", + "loc.keyboard.releasekey": "Отпускаем клавишу '{0}'", + "loc.keyboard.sendkeys": "Нажимаем клавиши '{0}'", + "loc.keyboard.sendkeys.withkeyhold": "Нажимаем клавиши '{0}' с зажатой клавишей '{1}'", + "loc.mouse.click": "Щёлкаем кнопку мыши", + "loc.mouse.clickandhold": "Зажимаем кнопку мыши", + "loc.mouse.release": "Отпускаем кнопку мыши", + "loc.mouse.contextclick": "Щёлкаем правую кнопку мыши", + "loc.mouse.doubleclick": "Дважды щёлкаем кнопку мыши", + "loc.mouse.movebyoffset": "Перемещаем курсор мыши на '{0}' пунктов по вертикали и '{1}' пунктов по горизонтали", + "loc.mouse.draganddrop": "Перемещаем к элементу '{0} '{1}''", + "loc.mouse.draganddrop.tooffset": "Перемещаем на '{0}' пунктов по вертикали и '{1}' пунктов по горизонтали", + "loc.mouse.movefromelement": "Отводим курсор мыши от элемента", + "loc.mouse.movetoelement": "Наводим курсор мыши на элемент", + "loc.mouse.movetoelement.byoffset": "Наводим курсор мыши на элемент со смещением на '{0}' пунктов по вертикали и '{1}' пунктов по горизонтали", + "loc.mouse.movetoelement.byoffset.withorigin": "Наводим курсор мыши на элемент со смещением на '{0}' пунктов по вертикали и '{1}' пунктов по горизонтали, отсчитывая от '{2}'", + "loc.mouse.scrollbyoffset": "Прокручиваем колёсико мыши на '{0}' пунктов по вертикали и '{1}' пунктов по горизонтали" } \ No newline at end of file diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Windows/Window.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Windows/Window.cs index d72703e..4dc3748 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Windows/Window.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Windows/Window.cs @@ -34,13 +34,13 @@ protected Window(By locator, string name) public string Name { get; } /// - /// Instance of logger + /// Instance of logger /// /// Logger instance. protected Logger Logger => ApplicationManager.GetRequiredService(); /// - /// Element factory + /// Element factory /// /// Element factory. protected IElementFactory ElementFactory => ApplicationManager.GetRequiredService(); diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Actions/KeyboardActionsTests.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Actions/KeyboardActionsTests.cs new file mode 100644 index 0000000..e9184ad --- /dev/null +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Actions/KeyboardActionsTests.cs @@ -0,0 +1,64 @@ +using Aquality.WinAppDriver.Actions; +using Aquality.WinAppDriver.Applications; +using Aquality.WinAppDriver.Elements.Interfaces; +using Aquality.WinAppDriver.Tests.Applications.Locators; +using NUnit.Framework; +using System; + +namespace Aquality.WinAppDriver.Tests.Actions +{ + public class KeyboardActionsTests : TestWithApplication + { + private const string ValueToSend = "abc"; + + protected virtual IKeyboardActions KeyboardActions => ApplicationManager.GetRequiredService(); + + protected ITextBox RightArgumentTextBox => new CalculatorWindow().RightArgumentTextBox; + + protected static readonly ModifierKey[] modifierKeys = Enum.GetValues(typeof(ModifierKey)) as ModifierKey[]; + + [Test] + public void Should_SendKeys_ViaKeyboardActions() + { + RightArgumentTextBox.Click(); + KeyboardActions.SendKeys(ValueToSend); + Assert.AreEqual(ValueToSend, RightArgumentTextBox.Value); + } + + [Test] + public void Should_PressKey_ViaKeyboardActions() + { + RightArgumentTextBox.Click(); + KeyboardActions.PressKey(ModifierKey.Shift); + KeyboardActions.SendKeys(ValueToSend); + KeyboardActions.ReleaseKey(ModifierKey.Shift); + Assert.AreEqual(ValueToSend.ToUpper(), RightArgumentTextBox.Value); + } + + [Test] + public void Should_SendKeysWithKeyHold_ViaKeyboardActions() + { + RightArgumentTextBox.Click(); + KeyboardActions.SendKeysWithKeyHold(ValueToSend, ModifierKey.Shift); + Assert.AreEqual(ValueToSend.ToUpper(), RightArgumentTextBox.Value); + } + + [Test] + public void Should_ReleaseKey_ViaKeyboardActions() + { + RightArgumentTextBox.Click(); + KeyboardActions.PressKey(ModifierKey.Shift); + KeyboardActions.SendKeys(ValueToSend); + KeyboardActions.ReleaseKey(ModifierKey.Shift); + KeyboardActions.SendKeys(ValueToSend); + Assert.AreEqual($"{ValueToSend.ToUpper()}{ValueToSend}", RightArgumentTextBox.Value); + } + + [Test] + public void Should_NotThrow_WhenHoldModifierKeys_ViaKeyboardActions([ValueSource(nameof(modifierKeys))] ModifierKey modifierKey) + { + RightArgumentTextBox.Click(); + Assert.DoesNotThrow(() => KeyboardActions.SendKeysWithKeyHold(ValueToSend, modifierKey)); + } + } +} diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Actions/MouseActionsTests.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Actions/MouseActionsTests.cs new file mode 100644 index 0000000..a948783 --- /dev/null +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Actions/MouseActionsTests.cs @@ -0,0 +1,31 @@ +using Aquality.WinAppDriver.Actions; +using Aquality.WinAppDriver.Applications; +using Aquality.WinAppDriver.Elements.Interfaces; +using Aquality.WinAppDriver.Tests.Applications.Locators; +using NUnit.Framework; + +namespace Aquality.WinAppDriver.Tests.Actions +{ + public class MouseActionsTests : TestWithApplication + { + protected virtual IMouseActions MouseActions => ApplicationManager.GetRequiredService(); + + protected ITextBox RightArgumentTextBox => new CalculatorWindow().RightArgumentTextBox; + + [Test] + public void Should_PerformMouseActions() + { + RightArgumentTextBox.Click(); + Assert.DoesNotThrow(() => + { + MouseActions.Click(); + MouseActions.ClickAndHold(); + MouseActions.Release(); + MouseActions.ContextClick(); + MouseActions.DoubleClick(); + MouseActions.MoveByOffset(10, 10); + MouseActions.Scroll(10, 10); + }); + } + } +} diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/KeyboardActionsTests.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/KeyboardActionsTests.cs new file mode 100644 index 0000000..faaa143 --- /dev/null +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/KeyboardActionsTests.cs @@ -0,0 +1,10 @@ +using Aquality.WinAppDriver.Actions; +using Aquality.WinAppDriver.Applications; + +namespace Aquality.WinAppDriver.Tests.Applications +{ + public class KeyboardActionsTests : Actions.KeyboardActionsTests + { + protected override IKeyboardActions KeyboardActions => ApplicationManager.Application.KeyboardActions; + } +} diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/Locators/CalculatorWindow.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/Locators/CalculatorWindow.cs index 4d5bbbc..dbefe7e 100644 --- a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/Locators/CalculatorWindow.cs +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/Locators/CalculatorWindow.cs @@ -9,6 +9,8 @@ public class CalculatorWindow : Window { private static By WindowLocator => By.TagName("Window"); + public ITextBox LeftArgumentTextBox => ElementFactory.GetTextBox(MobileBy.AccessibilityId("50"), "Left Argument"); + public ITextBox RightArgumentTextBox => ElementFactory.GetTextBox(By.XPath("//*[@AutomationId='49']"), "Right Argument"); public IButton OneButton => ElementFactory.GetButton(By.Name("1"), "1"); diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/MouseActionsTests.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/MouseActionsTests.cs new file mode 100644 index 0000000..f92483a --- /dev/null +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Applications/MouseActionsTests.cs @@ -0,0 +1,10 @@ +using Aquality.WinAppDriver.Actions; +using Aquality.WinAppDriver.Applications; + +namespace Aquality.WinAppDriver.Tests.Applications +{ + public class MouseActionsTests : Actions.MouseActionsTests + { + protected override IMouseActions MouseActions => ApplicationManager.Application.MouseActions; + } +} diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Elements/Actions/KeyboardActionsTests.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Elements/Actions/KeyboardActionsTests.cs new file mode 100644 index 0000000..8f0e79e --- /dev/null +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Elements/Actions/KeyboardActionsTests.cs @@ -0,0 +1,9 @@ +using Aquality.WinAppDriver.Actions; + +namespace Aquality.WinAppDriver.Tests.Elements.Actions +{ + public class KeyboardActionsTests : Tests.Actions.KeyboardActionsTests + { + protected override IKeyboardActions KeyboardActions => RightArgumentTextBox.KeyboardActions; + } +} diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Elements/Actions/MouseActionsTests.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Elements/Actions/MouseActionsTests.cs new file mode 100644 index 0000000..701d241 --- /dev/null +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Elements/Actions/MouseActionsTests.cs @@ -0,0 +1,32 @@ +using Aquality.WinAppDriver.Actions; +using Aquality.WinAppDriver.Elements.Interfaces; +using Aquality.WinAppDriver.Tests.Applications.Locators; +using NUnit.Framework; +using OpenQA.Selenium.Interactions; + +namespace Aquality.WinAppDriver.Tests.Elements.Actions +{ + public class MouseActionsTests : Tests.Actions.MouseActionsTests + { + protected override IMouseActions MouseActions => RightArgumentTextBox.MouseActions; + private ITextBox LeftArgumentTextBox => new CalculatorWindow().LeftArgumentTextBox; + + [Test] + public void Should_PerformElementSpecificMouseActions() + { + RightArgumentTextBox.Click(); + Assert.DoesNotThrow(() => + { + RightArgumentTextBox.MouseActions.DragAndDrop(LeftArgumentTextBox); + RightArgumentTextBox.MouseActions.DragAndDropToOffset(10, 10); + RightArgumentTextBox.MouseActions.MoveByOffset(10, 10); + RightArgumentTextBox.MouseActions.MoveFromElement(); + RightArgumentTextBox.MouseActions.MoveToElement(); + RightArgumentTextBox.MouseActions.MoveToElement(10, 10); + RightArgumentTextBox.MouseActions.MoveToElement(10, 10, MoveToElementOffsetOrigin.Center); + RightArgumentTextBox.MouseActions.MoveToElement(10, 10, MoveToElementOffsetOrigin.TopLeft); + RightArgumentTextBox.MouseActions.Scroll(10, 10); + }); + } + } +} diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Localization/LocalizationFilesTest.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Localization/LocalizationFilesTest.cs index c057c34..2b9f40a 100644 --- a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Localization/LocalizationFilesTest.cs +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Localization/LocalizationFilesTest.cs @@ -13,14 +13,14 @@ namespace Aquality.WinAppDriver.Tests.Localization { public class LocalizationFilesTest { - private static readonly string[] SupportedLanguages = { "be", "en", "ru" }; + private const string EnglishLanguageCode = "en"; + private static readonly string[] SupportedLanguages = { "be", EnglishLanguageCode, "ru" }; private static readonly Assembly LibraryAssembly = Assembly.GetAssembly(typeof(ApplicationManager)); - private static readonly IList> LocalizationFileDictionary - = new JsonFile("Resources.Localization.en.json", LibraryAssembly).GetValue>("$").ToList(); - private static readonly IEnumerable KeysWithoutParams = LocalizationFileDictionary.Where(pair => !pair.Value.Contains("{0}")).Select(pair => pair.Key); - private static readonly IEnumerable KeysWithOneParameter = LocalizationFileDictionary.Where(pair => pair.Value.Contains("{0}") && !pair.Value.Contains("{1}")).Select(pair => pair.Key); - private static readonly IEnumerable KeysWithTwoAndMoreParameters = LocalizationFileDictionary.Where(pair => pair.Value.Contains("{1}")).Select(pair => pair.Key); - private static readonly IEnumerable KeysWithParameters = LocalizationFileDictionary.Where(pair => pair.Value.Contains("{0}")).Select(pair => pair.Key); + private static readonly IList> LocalizationFileEnglishDictionary = GetLocalizationDictionaryAsList(EnglishLanguageCode); + private static readonly IEnumerable KeysWithoutParams = LocalizationFileEnglishDictionary.Where(pair => !pair.Value.Contains("{0}")).Select(pair => pair.Key); + private static readonly IEnumerable KeysWithOneParameter = LocalizationFileEnglishDictionary.Where(pair => pair.Value.Contains("{0}") && !pair.Value.Contains("{1}")).Select(pair => pair.Key); + private static readonly IEnumerable KeysWithTwoAndMoreParameters = LocalizationFileEnglishDictionary.Where(pair => pair.Value.Contains("{1}")).Select(pair => pair.Key); + private static readonly IEnumerable KeysWithParameters = LocalizationFileEnglishDictionary.Where(pair => pair.Value.Contains("{0}")).Select(pair => pair.Key); private LocalizationManager LocalizationManager => ApplicationManager.GetRequiredService(); @@ -58,7 +58,7 @@ public void Should_ReturnNonKeyValues_AndNotEmptyValues_ForKeysWithOneParameter( [Test] public void Should_ReturnNonKeyValues_AndNotEmptyValues_ForKeysWithTwoAndMoreParameters([ValueSource(nameof(SupportedLanguages))] string language, [ValueSource(nameof(KeysWithTwoAndMoreParameters))] string key) { - var paramsArray = new[] { "a", "b" }; + var paramsArray = new[] { "a", "b" , "c", "d" }; var localizedValue = GetLocalizationManager(language).GetLocalizedMessage(key, paramsArray); Assert.AreNotEqual(key, localizedValue, "Value should be defined in resource files"); Assert.IsNotEmpty(localizedValue, "Value should not be empty"); @@ -72,6 +72,28 @@ public void Should_ThrowsFormatException_WhenKeysRequireParams([ValueSource(name Assert.Throws(() => GetLocalizationManager(language).GetLocalizedMessage(key)); } + [Test] + public void Should_HaveSameAmountOfValues([ValueSource(nameof(SupportedLanguages))] string language) + { + Assert.AreEqual(LocalizationFileEnglishDictionary.Count, GetLocalizationDictionaryAsList(language).Count); + } + + [Test] + public void Should_HaveNotAllValuesTheSame_InDifferentLanguages([ValueSource(nameof(SupportedLanguages))] string language) + { + var currentLanguageDictionary = GetLocalizationDictionaryAsList(language); + + foreach (var dictionary in SupportedLanguages.Except(new[] { language }).Select(lang => GetLocalizationDictionaryAsList(lang))) + { + CollectionAssert.AreNotEquivalent(currentLanguageDictionary, dictionary); + } + } + + private static IList> GetLocalizationDictionaryAsList(string language) + { + return new JsonFile($"Resources.Localization.{language}.json", LibraryAssembly).GetValue>("$").ToList(); + } + private LocalizationManager GetLocalizationManager(string customLanguage) { var configuration = new DynamicConfiguration