// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Interactions;
namespace Microsoft.PowerToys.UITest
{
///
/// Provides interfaces for interacting with UI elements.
///
public class Session
{
private WindowsDriver Root { get; set; }
private WindowsDriver WindowsDriver { get; set; }
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(nint hWnd);
public Session(WindowsDriver root, WindowsDriver windowsDriver)
{
this.Root = root;
this.WindowsDriver = windowsDriver;
}
///
/// Finds an element by selector.
///
/// The class of the element, should be Element or its derived class.
/// The selector to find the element.
/// The timeout in milliseconds (default is 3000).
/// The found element.
public T Find(By by, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
// leverage findAll to filter out mismatched elements
var collection = this.FindAll(by, timeoutMS);
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
return collection[0];
}
///
/// Shortcut for this.Find(By.Name(name), timeoutMS)
///
/// The class of the element, should be Element or its derived class.
/// The name of the element.
/// The timeout in milliseconds (default is 3000).
/// The found element.
public T Find(string name, int timeoutMS = 3000)
where T : Element, new()
{
return this.Find(By.Name(name), timeoutMS);
}
///
/// Shortcut for this.Find(by, timeoutMS)
///
/// The selector to find the element.
/// The timeout in milliseconds (default is 3000).
/// The found element.
public Element Find(By by, int timeoutMS = 3000)
{
return this.Find(by, timeoutMS);
}
///
/// Shortcut for this.Find(By.Name(name), timeoutMS)
///
/// The name of the element.
/// The timeout in milliseconds (default is 3000).
/// The found element.
public Element Find(string name, int timeoutMS = 3000)
{
return this.Find(By.Name(name), timeoutMS);
}
///
/// Finds all elements by selector.
///
/// The class of the elements, should be Element or its derived class.
/// The selector to find the elements.
/// The timeout in milliseconds (default is 3000).
/// A read-only collection of the found elements.
public ReadOnlyCollection FindAll(By by, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
var foundElements = FindHelper.FindAll(
() =>
{
if (by.GetIsAccessibilityId())
{
var elements = this.WindowsDriver.FindElementsByAccessibilityId(by.GetAccessibilityId());
return elements;
}
else
{
var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy());
return elements;
}
},
this.WindowsDriver,
timeoutMS);
return foundElements ?? new ReadOnlyCollection([]);
}
///
/// Finds all elements by selector.
/// Shortcut for this.FindAll(By.Name(name), timeoutMS)
///
/// The class of the elements, should be Element or its derived class.
/// The name to find the elements.
/// The timeout in milliseconds (default is 3000).
/// A read-only collection of the found elements.
public ReadOnlyCollection FindAll(string name, int timeoutMS = 3000)
where T : Element, new()
{
return this.FindAll(By.Name(name), timeoutMS);
}
///
/// Finds all elements by selector.
/// Shortcut for this.FindAll(by, timeoutMS)
///
/// The selector to find the elements.
/// The timeout in milliseconds (default is 3000).
/// A read-only collection of the found elements.
public ReadOnlyCollection FindAll(By by, int timeoutMS = 3000)
{
return this.FindAll(by, timeoutMS);
}
///
/// Finds all elements by selector.
/// Shortcut for this.FindAll(By.Name(name), timeoutMS)
///
/// The name to find the elements.
/// The timeout in milliseconds (default is 3000).
/// A read-only collection of the found elements.
public ReadOnlyCollection FindAll(string name, int timeoutMS = 3000)
{
return this.FindAll(By.Name(name), timeoutMS);
}
///
/// Keyboard Action key.
///
/// The Keys1 to click.
/// The Keys2 to click.
/// The Keys3 to click.
/// The Keys4 to click.
public void KeyboardAction(string key1, string key2 = "", string key3 = "", string key4 = "")
{
PerformAction((actions, windowElement) =>
{
if (string.IsNullOrEmpty(key2))
{
actions.SendKeys(key1);
}
else if (string.IsNullOrEmpty(key3))
{
actions.SendKeys(key1).SendKeys(key2);
}
else if (string.IsNullOrEmpty(key4))
{
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3);
}
else
{
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3).SendKeys(key4);
}
actions.Release();
actions.Build().Perform();
});
}
///
/// Attaches to an existing PowerToys module.
///
/// The PowerToys module to attach to.
/// The attached session.
public Session Attach(PowerToysModule module)
{
string windowName = ModuleConfigData.Instance.GetModuleWindowName(module);
return this.Attach(windowName);
}
///
/// Attaches to an existing exe by string window name.
/// The session should be attached when a new app is started.
///
/// The window name to attach to.
/// The attached session.
public Session Attach(string windowName)
{
if (this.Root != null)
{
var window = this.Root.FindElementByName(windowName);
Assert.IsNotNull(window, $"Failed to attach. Window '{windowName}' not found");
var windowHandle = new nint(int.Parse(window.GetAttribute("NativeWindowHandle")));
SetForegroundWindow(windowHandle);
var hexWindowHandle = windowHandle.ToString("x");
var appCapabilities = new AppiumOptions();
appCapabilities.AddAdditionalCapability("appTopLevelWindow", hexWindowHandle);
appCapabilities.AddAdditionalCapability("deviceName", "WindowsPC");
this.WindowsDriver = new WindowsDriver(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), appCapabilities);
Assert.IsNotNull(this.WindowsDriver, "Attach WindowsDriver is null");
// Set implicit timeout to make element search retry every 500 ms
this.WindowsDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(3);
}
else
{
Assert.IsNotNull(this.Root, $"Failed to attach to the window '{windowName}'. Root driver is null");
}
return this;
}
///
/// Simulates a manual operation on the element.
///
/// The action to perform on the element.
/// The number of milliseconds to wait before the action. Default value is 500 ms
/// The number of milliseconds to wait after the action. Default value is 500 ms
protected void PerformAction(Action> action, int msPreAction = 500, int msPostAction = 500)
{
if (msPreAction > 0)
{
Task.Delay(msPreAction).Wait();
}
var windowsDriver = this.WindowsDriver;
Actions actions = new Actions(this.WindowsDriver);
action(actions, windowsDriver);
if (msPostAction > 0)
{
Task.Delay(msPostAction).Wait();
}
}
}
}