Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/keyboard and mouse actions #12

Merged
merged 13 commits into from
Sep 20, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
namespace Aquality.WinAppDriver.Actions
{
/// <summary>
/// Provides methods representing basic keyboard actions.
/// </summary>
public interface IKeyboardActions
{
/// <summary>
/// Presses a key.
/// </summary>
/// <param name="keyToPress">The key value representing the key to press.</param>
/// <remarks>The key value must be one of the values from the <see cref="OpenQA.Selenium.Keys"/> class.</remarks>
/// <exception cref="System.ArgumentException">If the key sent is not is not one of:
/// <see cref="OpenQA.Selenium.Keys.Shift"/>,
/// <see cref="OpenQA.Selenium.Keys.Control"/>,
/// <see cref="OpenQA.Selenium.Keys.Alt"/>,
/// <see cref="OpenQA.Selenium.Keys.Meta"/>,
/// <see cref="OpenQA.Selenium.Keys.Command"/>,
/// <see cref="OpenQA.Selenium.Keys.LeftAlt"/>,
/// <see cref="OpenQA.Selenium.Keys.LeftShift"/>,
/// <see cref="OpenQA.Selenium.Keys.Shift"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really need this list of Keys in summary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I do. Selenium has it. We either need to follow it's notation, or define a custom Enum with mapping to this values, and accept only them in our methods. I like the first variant more, but we can discuss it

/// </exception>
void PressKey(string keyToPress);

/// <summary>
/// Releases a key.
/// </summary>
/// <param name="keyToRelease">The key value representing the key to release.</param>
/// <remarks>The key value must be one of the values from the <see cref="OpenQA.Selenium.Keys"/> class.</remarks>
/// <exception cref="System.ArgumentException">If the key sent is not is not one of:
/// <see cref="OpenQA.Selenium.Keys.Shift"/>,
/// <see cref="OpenQA.Selenium.Keys.Control"/>,
/// <see cref="OpenQA.Selenium.Keys.Alt"/>,
/// <see cref="OpenQA.Selenium.Keys.Meta"/>,
/// <see cref="OpenQA.Selenium.Keys.Command"/>,
/// <see cref="OpenQA.Selenium.Keys.LeftAlt"/>,
/// <see cref="OpenQA.Selenium.Keys.LeftShift"/>,
/// <see cref="OpenQA.Selenium.Keys.Shift"/>
/// </exception>
void ReleaseKey(string keyToRelease);

/// <summary>
/// Sends a sequence of keystrokes to the target.
/// </summary>
/// <param name="keySequence">A string representing the keystrokes to send.</param>
void SendKeys(string keySequence);

/// <summary>
/// Sends a sequence of keystrokes to the target, holding a specified key.
/// After the action, holded key is released.
/// </summary>
/// <param name="keySequence">A string representing the keystrokes to send.</param>
/// <param name="keyToHold">The key value representing the key to hold.
/// <remarks>The key value must be one of the values from the <see cref="OpenQA.Selenium.Keys"/> class.</remarks></param>
/// <exception cref="System.ArgumentException">If the key sent is not is not one of:
/// <see cref="OpenQA.Selenium.Keys.Shift"/>,
/// <see cref="OpenQA.Selenium.Keys.Control"/>,
/// <see cref="OpenQA.Selenium.Keys.Alt"/>,
/// <see cref="OpenQA.Selenium.Keys.Meta"/>,
/// <see cref="OpenQA.Selenium.Keys.Command"/>,
/// <see cref="OpenQA.Selenium.Keys.LeftAlt"/>,
/// <see cref="OpenQA.Selenium.Keys.LeftShift"/>,
/// <see cref="OpenQA.Selenium.Keys.Shift"/>
/// </exception>
void SendKeysWithKeyHold(string keySequence, string keyToHold);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Aquality.Selenium.Core.Localization;
using Aquality.WinAppDriver.Extensions;
using OpenQA.Selenium.Appium.Windows;
using System;
using SeleniumActions = OpenQA.Selenium.Interactions.Actions;

namespace Aquality.WinAppDriver.Actions
{
/// <summary>
/// Implements Keyboard actions for the whole application.
/// </summary>
public class KeyboardActions : IKeyboardActions
{
private readonly LocalizationLogger localizationLogger;
private readonly Func<WindowsDriver<WindowsElement>> windowsDriverSupplier;

public KeyboardActions(LocalizationLogger localizationLogger, Func<WindowsDriver<WindowsElement>> windowsDriverSupplier)
{
this.localizationLogger = localizationLogger;
this.windowsDriverSupplier = windowsDriverSupplier;
}

public void PressKey(string keyToPress)
{
LogAction("loc.keyboard.presskey", keyToPress.GetLoggableValueForKeyboardKey());
PerformAction(actions => actions.KeyDown(keyToPress));
}

public void ReleaseKey(string keyToRelease)
{
LogAction("loc.keyboard.releasekey", keyToRelease.GetLoggableValueForKeyboardKey());
PerformAction(actions => actions.KeyUp(keyToRelease));
}

public void SendKeys(string keySequence)
{
LogAction("loc.keyboard.sendkeys", keySequence);
PerformAction(actions => actions.SendKeys(keySequence));
}

public void SendKeysWithKeyHold(string keySequence, string keyToHold)
{
LogAction("loc.keyboard.sendkeys.withkeyhold", keySequence, keyToHold.GetLoggableValueForKeyboardKey());
PerformAction(actions => actions.KeyDown(keyToHold).SendKeys(keySequence).KeyUp(keyToHold));
}

/// <summary>
/// Performs submitted action against new <see cref="SeleniumActions"/> object.
/// </summary>
/// <param name="action">Action to be performed.</param>
protected virtual void PerformAction(Func<SeleniumActions, SeleniumActions> action)
{
action(new SeleniumActions(windowsDriverSupplier())).Build().Perform();
}

/// <summary>
/// Logs keyboard action in specific format.
/// </summary>
/// <param name="messageKey">Key of the localized message.</param>
/// <param name="args">Arguments for the localized message.</param>
protected virtual void LogAction(string messageKey, params object[] args)
{
localizationLogger.Info(messageKey, args);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Aquality.Selenium.Core.Applications;
using Aquality.Selenium.Core.Configurations;
using Aquality.Selenium.Core.Localization;
using Aquality.WinAppDriver.Actions;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Remote;

Expand All @@ -20,11 +21,12 @@ public class Application : IApplication
/// <param name="windowsDriver">Instance of WinAppDriver</param>
/// <param name="timeoutConfiguration">Instance of <see cref="ITimeoutConfiguration"/></param>
/// <param name="logger">Instance of <see cref="LocalizationLogger"/></param>
public Application(WindowsDriver<WindowsElement> windowsDriver, ITimeoutConfiguration timeoutConfiguration, LocalizationLogger logger)
public Application(WindowsDriver<WindowsElement> windowsDriver, ITimeoutConfiguration timeoutConfiguration, LocalizationLogger logger, IKeyboardActions keyboardActions)
{
Logger = logger;
WindowsDriver = windowsDriver;
WindowsDriver.Manage().Timeouts().ImplicitWait = timeoutConfiguration.Implicit;
KeyboardActions = keyboardActions;
logger.Info("loc.application.ready");
}

Expand All @@ -37,6 +39,8 @@ public Application(WindowsDriver<WindowsElement> windowsDriver, ITimeoutConfigur
/// </summary>
public WindowsDriver<WindowsElement> WindowsDriver { get; }

public IKeyboardActions KeyboardActions { get; }

/// <summary>
/// Sets WinAppDriver ImplicitWait timeout.
/// Default value: <see cref="ITimeoutConfiguration.Implicit"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using OpenQA.Selenium.Appium.Service;
using Aquality.Selenium.Core.Logging;
using System.Reflection;
using Aquality.WinAppDriver.Actions;
mialeska marked this conversation as resolved.
Show resolved Hide resolved

namespace Aquality.WinAppDriver.Applications
{
Expand Down Expand Up @@ -69,15 +70,16 @@ public static void SetDefaultFactory()
var driverSettings = GetRequiredService<IDriverSettings>();
var localizationLogger = GetRequiredService<LocalizationLogger>();
var timeoutConfiguration = GetRequiredService<ITimeoutConfiguration>();
var keyboardActions = GetRequiredService<IKeyboardActions>();

IApplicationFactory applicationFactory;
if (appProfile.IsRemote)
{
applicationFactory = new RemoteApplicationFactory(appProfile.RemoteConnectionUrl, driverSettings, timeoutConfiguration, localizationLogger);
applicationFactory = new RemoteApplicationFactory(appProfile.RemoteConnectionUrl, driverSettings, timeoutConfiguration, localizationLogger, keyboardActions);
}
else
{
applicationFactory = new LocalApplicationFactory(AppiumLocalServiceContainer.Value, driverSettings, timeoutConfiguration, localizationLogger);
applicationFactory = new LocalApplicationFactory(AppiumLocalServiceContainer.Value, driverSettings, timeoutConfiguration, localizationLogger, keyboardActions);
}

SetFactory(applicationFactory);
Expand All @@ -100,10 +102,10 @@ private static IServiceCollection RegisterServices(Func<IServiceProvider, Applic
startup.ConfigureServices(services, applicationSupplier, settingsFile);
services.AddTransient<IElementFactory, ElementFactory>();
services.AddTransient<CoreElementFactory, ElementFactory>();
var driverSettings = new DriverSettings(settingsFile);
services.AddSingleton<IDriverSettings>(driverSettings);
services.AddSingleton<IApplicationProfile>(new ApplicationProfile(settingsFile, driverSettings));
services.AddSingleton(new LocalizationManager(new LoggerConfiguration(settingsFile), Logger.Instance, Assembly.GetExecutingAssembly()));
services.AddSingleton<IDriverSettings>(serviceProvider => new DriverSettings(settingsFile));
services.AddSingleton<IApplicationProfile>(serviceProvider => new ApplicationProfile(settingsFile, serviceProvider.GetRequiredService<IDriverSettings>()));
services.AddSingleton(serviceProvider => new LocalizationManager(serviceProvider.GetRequiredService<ILoggerConfiguration>(), serviceProvider.GetRequiredService<Logger>(), Assembly.GetExecutingAssembly()));
services.AddSingleton<IKeyboardActions>(serviceProvider => new KeyboardActions(serviceProvider.GetRequiredService<LocalizationLogger>(), () => Application.WindowsDriver));
return services;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Aquality.Selenium.Core.Configurations;
using Aquality.Selenium.Core.Localization;
using Aquality.WinAppDriver.Actions;
using Aquality.WinAppDriver.Configurations;
using OpenQA.Selenium.Appium.Service;

Expand All @@ -11,13 +12,16 @@ public class LocalApplicationFactory : ApplicationFactory
private readonly IDriverSettings driverSettings;
private readonly ITimeoutConfiguration timeoutConfiguration;
private readonly LocalizationLogger localizationLogger;
private readonly IKeyboardActions keyboardActions;

public LocalApplicationFactory(AppiumLocalService driverService, IDriverSettings driverSettings, ITimeoutConfiguration timeoutConfiguration, LocalizationLogger localizationLogger) : base(localizationLogger)
public LocalApplicationFactory(AppiumLocalService driverService, IDriverSettings driverSettings, ITimeoutConfiguration timeoutConfiguration, LocalizationLogger localizationLogger, IKeyboardActions keyboardActions)
: base(localizationLogger)
{
this.driverService = driverService;
this.driverSettings = driverSettings;
this.timeoutConfiguration = timeoutConfiguration;
this.localizationLogger = localizationLogger;
this.keyboardActions = keyboardActions;
}

public override Application Application
Expand All @@ -28,7 +32,7 @@ public override Application Application
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);
return new Application(driver, timeoutConfiguration, localizationLogger, keyboardActions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks very strange. Why do you need to pass actions to the Application?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in order to use the same instance of the keyboardActions, I guess

}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Aquality.Selenium.Core.Configurations;
using Aquality.Selenium.Core.Localization;
using Aquality.WinAppDriver.Actions;
using Aquality.WinAppDriver.Configurations;
using OpenQA.Selenium.Remote;
using System;
Expand All @@ -12,13 +13,16 @@ public class RemoteApplicationFactory : ApplicationFactory
private readonly IDriverSettings driverSettings;
private readonly ITimeoutConfiguration timeoutConfiguration;
private readonly LocalizationLogger localizationLogger;
private readonly IKeyboardActions keyboardActions;

public RemoteApplicationFactory(Uri driverServerUri, IDriverSettings driverSettings, ITimeoutConfiguration timeoutConfiguration, LocalizationLogger localizationLogger) : base(localizationLogger)
public RemoteApplicationFactory(Uri driverServerUri, IDriverSettings driverSettings, ITimeoutConfiguration timeoutConfiguration, LocalizationLogger localizationLogger, IKeyboardActions keyboardActions)
: base(localizationLogger)
{
this.driverServerUri = driverServerUri;
this.driverSettings = driverSettings;
this.timeoutConfiguration = timeoutConfiguration;
this.localizationLogger = localizationLogger;
this.keyboardActions = keyboardActions;
}

public override Application Application
Expand All @@ -28,7 +32,7 @@ public override Application Application
localizationLogger.Info("loc.application.driver.service.remote", driverServerUri);
var driver = GetDriver(driverServerUri, driverSettings.AppiumOptions, timeoutConfiguration.Command);
driver.FileDetector = new LocalFileDetector();
return new Application(driver, timeoutConfiguration, localizationLogger);
return new Application(driver, timeoutConfiguration, localizationLogger, keyboardActions);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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 Aquality.WinAppDriver.Extensions;
using OpenQA.Selenium;
using System;
using SeleniumActions = OpenQA.Selenium.Interactions.Actions;

namespace Aquality.WinAppDriver.Elements.Actions
{
/// <summary>
/// Implements Keyboard actions for a specific element.
mialeska marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public class KeyboardActions : IKeyboardActions
{
private readonly IElement element;
private readonly string elementType;
private readonly Func<IApplication> applicationSupplier;
private readonly LocalizationLogger localizationLogger;
private readonly ElementActionRetrier elementActionsRetrier;

/// <summary>
/// Instantiates Keyboard actions for a specific element.
/// </summary>
/// <param name="element">Target element.</param>
/// <param name="elementType">Target element's type.</param>
/// <param name="applicationSupplier">Method to get current application session.</param>
/// <param name="localizationLogger">Logger for localized values.</param>
/// <param name="elementActionsRetrier">Retrier for element actions.</param>
public KeyboardActions(IElement element, string elementType, Func<IApplication> applicationSupplier, LocalizationLogger localizationLogger, ElementActionRetrier elementActionsRetrier)
{
this.element = element;
this.elementType = elementType;
this.applicationSupplier = applicationSupplier;
this.localizationLogger = localizationLogger;
this.elementActionsRetrier = elementActionsRetrier;
}

public void PressKey(string keyToPress)
{
LogElementAction("loc.keyboard.presskey", keyToPress.GetLoggableValueForKeyboardKey());
PerformAction((actions, element) => actions.KeyDown(element, keyToPress));
}

public void ReleaseKey(string keyToRelease)
{
LogElementAction("loc.keyboard.releasekey", keyToRelease.GetLoggableValueForKeyboardKey());
PerformAction((actions, element) => actions.KeyUp(element, keyToRelease));
}

public void SendKeys(string keySequence)
{
LogElementAction("loc.keyboard.sendkeys", keySequence);
PerformAction((actions, element) => actions.SendKeys(element, keySequence));
}

public void SendKeysWithKeyHold(string keySequence, string keyToHold)
{
LogElementAction("loc.keyboard.sendkeys.withkeyhold", keySequence, keyToHold.GetLoggableValueForKeyboardKey());
PerformAction((actions, element) => actions.KeyDown(element, keyToHold).SendKeys(element, keySequence).KeyUp(element, keyToHold));
}

/// <summary>
/// Performs submtted action against new <see cref="SeleniumActions"/> object.
mialeska marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="action">Action to be performed.</param>
protected virtual void PerformAction(Func<SeleniumActions, IWebElement, SeleniumActions> action)
{
elementActionsRetrier.DoWithRetry(() =>
{
action(new SeleniumActions(applicationSupplier().Driver), element.GetElement()).Build().Perform();
});
}

/// <summary>
/// Logs element action in specific format.
/// </summary>
/// <param name="messageKey">Key of the localized message.</param>
/// <param name="args">Arguments for the localized message.</param>
protected virtual void LogElementAction(string messageKey, params object[] args)
{
localizationLogger.InfoElementAction(elementType, element.Name, messageKey, args);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
using Aquality.Selenium.Core.Localization;
using Aquality.Selenium.Core.Utilities;
using Aquality.Selenium.Core.Waitings;
using Aquality.WinAppDriver.Actions;
using Aquality.WinAppDriver.Applications;
using Aquality.WinAppDriver.Elements.Interfaces;
using OpenQA.Selenium;
using KeyboardActions = Aquality.WinAppDriver.Elements.Actions.KeyboardActions;
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)
{
Expand All @@ -26,7 +28,9 @@ protected Element(By locator, string name) : base(locator, name, ElementState.Di

protected override CoreElementFactory Factory => CustomFactory;

protected IElementFactory CustomFactory => ApplicationManager.GetRequiredService<IElementFactory>();
protected virtual IElementFactory CustomFactory => ApplicationManager.GetRequiredService<IElementFactory>();

public virtual IKeyboardActions KeyboardActions => new KeyboardActions(this, ElementType, () => Application, LocalizationLogger, ActionRetrier);

public T FindChildElement<T>(By childLocator, ElementSupplier<T> supplier = null) where T : IElement
{
Expand Down
Loading