Skip to content

Commit 8c6f7f6

Browse files
authored
Merge pull request #1 from Donsezan/UiElements
Implimentation UI Automation with selector pattern
2 parents c167f8b + 1b0f33f commit 8c6f7f6

29 files changed

+1737
-112
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ UiAutomationGRPC
5858
│ ├── UiAutomationServiceImplementation.cs # Core logic
5959
│ └── UiAutomationGRPC.Server.csproj
6060
├── UiAutomationGRPC.Client # gRPC Client sample
61-
│ ├── Program.cs # Client entry point
62-
│ └── UiAutomationGRPC.Client.csproj
6361
└── README.md
6462
```
6563

@@ -76,11 +74,12 @@ The service is defined in `uiautomation.proto`.
7674
| `GetProperty` | Retrieve a specific property value from an element. |
7775
| `OpenApp` | Launch an application by name or path. |
7876
| `Reflect` | query metadata about supported automation properties and patterns. |
77+
| `...` | ... |
7978

8079
### Key Concepts
8180

8281
- **Runtime ID**: A string handle returned by `FindElement` that uniquely identifies a UI element interface for subsequent calls.
83-
- **Conditions**: The search query language supports `PropertyCondition` (Name=X), `BoolCondition` (AND/OR/NOT), giving you flexibility in finding elements.
82+
8483

8584
## License
8685

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace UiAutomationGRPC.Client.Calc.Pages
2+
{
3+
public abstract class BasePageObject<T> where T : BasePageObject<T>
4+
{
5+
protected readonly CalcPageLocators Locators;
6+
}
7+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using System.Windows.Automation;
7+
using UiAutomation;
8+
using UiAutomationGRPC.Client.Framework;
9+
10+
namespace UiAutomationGRPC.Client.Calc.Pages
11+
{
12+
public class CalcNavigationPaget<TPage> where TPage : BasePageObject<TPage>
13+
{
14+
private readonly TPage _previousPage;
15+
private readonly UiAutomationService.UiAutomationServiceClient _client;
16+
private readonly CalcNavigationPagetLocators _locators;
17+
public CalcNavigationPaget(UiAutomationService.UiAutomationServiceClient client, TPage previousPage)
18+
{
19+
_client = client ?? throw new ArgumentNullException(nameof(client));
20+
_previousPage = previousPage;
21+
_locators = new CalcNavigationPagetLocators(client);
22+
// Optional: Wait for the app to be ready in constructor
23+
_locators.ButtonSettings.WaitForElementExist();
24+
}
25+
26+
public CalcSettingsPage<TPage> ClickSettings()
27+
{
28+
_locators.ButtonSettings.Click();
29+
return new CalcSettingsPage<TPage>(_client, _previousPage);
30+
}
31+
public TPage ClickNavigationButton()
32+
{
33+
_locators.ButtonNavigation.Click();
34+
return _previousPage;
35+
}
36+
37+
}
38+
public class CalcNavigationPagetLocators
39+
{
40+
private readonly UiAutomationService.UiAutomationServiceClient _client;
41+
42+
public CalcNavigationPagetLocators(UiAutomationService.UiAutomationServiceClient client) => _client = client;
43+
44+
private IAutomationElement CreateElement(Func<BaseSelector> selector) => new UiAutomationAdapter(_client, selector);
45+
46+
private Selector Window => new Selector(new PropertyConditions().NameProperty("Calculator"));
47+
48+
private IAutomationElement Element(string automationId) =>
49+
CreateElement(() => Window.Descendants(new PropertyConditions().AutomationIdProperty(automationId)));
50+
51+
public IAutomationElement ButtonSettings => Element("num1Button");
52+
public IAutomationElement ButtonNavigation => Element("GlobalNavButton");
53+
54+
}
55+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using System.Drawing;
3+
using System.Windows.Automation;
4+
using System.Xml.Linq;
5+
using UiAutomation;
6+
using UiAutomationGRPC.Client.Framework;
7+
8+
namespace UiAutomationGRPC.Client.Calc.Pages
9+
{
10+
public class CalcPage : BasePageObject<CalcPage>
11+
{
12+
private readonly UiAutomationService.UiAutomationServiceClient _client;
13+
private readonly CalcPageLocators _locators;
14+
public CalcPage(UiAutomationService.UiAutomationServiceClient client)
15+
{
16+
// Optional: Wait for the app to be ready in constructor
17+
_client = client;
18+
_locators = new CalcPageLocators(client);
19+
_locators.ResultText.WaitForElementExist();
20+
}
21+
22+
public CalcPage ClickTwo()
23+
{
24+
_locators.ButtonTwo.Click();
25+
return this;
26+
}
27+
28+
public CalcPage ClickPlus()
29+
{
30+
_locators.ButtonPlus.Click();
31+
return this;
32+
}
33+
34+
public CalcPage ClickEqual()
35+
{
36+
_locators.ButtonEqual.Click();
37+
return this;
38+
}
39+
40+
public string GetResult()
41+
{
42+
return _locators.ResultText.Name();
43+
}
44+
45+
public CalcNavigationPaget<CalcPage> ClickNavigationButton()
46+
{
47+
_locators.ResultText.Name();
48+
return new CalcNavigationPaget<CalcPage>(_client, this);
49+
}
50+
51+
public CalcPage ClickResultText()
52+
{
53+
_locators.ResultText.Click();
54+
return this;
55+
}
56+
}
57+
58+
public class CalcPageLocators
59+
{
60+
private readonly UiAutomationService.UiAutomationServiceClient _client;
61+
62+
public CalcPageLocators(UiAutomationService.UiAutomationServiceClient client) => _client = client;
63+
64+
private IAutomationElement CreateElement(Func<BaseSelector> selector) => new UiAutomationAdapter(_client, selector);
65+
66+
private Selector Window => new Selector(new PropertyConditions().NameProperty("Calculator"));
67+
68+
private IAutomationElement E(string automationId) =>
69+
CreateElement(() => Window.Descendants(new PropertyConditions().AutomationIdProperty(automationId)));
70+
71+
public IAutomationElement ButtonOne => E("num1Button");
72+
public IAutomationElement ButtonTwo => E("num2Button");
73+
public IAutomationElement ButtonPlus => E("plusButton");
74+
public IAutomationElement ButtonEqual => E("equalButton");
75+
public IAutomationElement ResultText => E("CalculatorResults");
76+
public IAutomationElement NavigationButton => CreateElement(() => Window.Descendants().ControlType("Button").NameContain("Close Navigation"));
77+
78+
79+
}
80+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using UiAutomation;
3+
using UiAutomationGRPC.Client.Framework;
4+
5+
namespace UiAutomationGRPC.Client.Calc.Pages
6+
{
7+
public class CalcSettingsPage<TPage> where TPage : BasePageObject<TPage>
8+
{
9+
private readonly TPage _previousPage;
10+
private readonly UiAutomationService.UiAutomationServiceClient _client;
11+
private readonly CalcSettingsPageLocators _locators;
12+
public CalcSettingsPage(UiAutomationService.UiAutomationServiceClient client, TPage previousPage)
13+
{
14+
_client = client ?? throw new ArgumentNullException(nameof(client));
15+
_previousPage = previousPage;
16+
_locators = new CalcSettingsPageLocators(client);
17+
// Optional: Wait for the app to be ready in constructor
18+
_locators.BackButton.WaitForElementExist();
19+
}
20+
21+
public CalcPage ClickBack()
22+
{
23+
_locators.BackButton.Click();
24+
return new CalcPage(_client);
25+
}
26+
}
27+
public class CalcSettingsPageLocators
28+
{
29+
private readonly UiAutomationService.UiAutomationServiceClient _client;
30+
31+
public CalcSettingsPageLocators(UiAutomationService.UiAutomationServiceClient client) => _client = client;
32+
33+
private IAutomationElement CreateElement(Func<BaseSelector> selector) => new UiAutomationAdapter(_client, selector);
34+
35+
private Selector Window => new Selector(new PropertyConditions().NameProperty("Calculator"));
36+
37+
public IAutomationElement BackButton => CreateElement(() => Window.Descendants().NameContain("Back"));
38+
39+
}
40+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Windows.Automation;
3+
using UiAutomation;
4+
5+
namespace UiAutomationGRPC.Client.Framework.Helpers
6+
{
7+
public static class ConditionConverter
8+
{
9+
public static UiAutomation.Condition Convert(System.Windows.Automation.Condition condition)
10+
{
11+
if (condition is System.Windows.Automation.PropertyCondition propCond)
12+
{
13+
return new UiAutomation.Condition
14+
{
15+
PropertyCondition = new UiAutomation.PropertyCondition
16+
{
17+
PropertyName = propCond.Property.ProgrammaticName.Replace("AutomationElementIdentifiers.", "").Replace("Property", ""),
18+
PropertyValue = propCond.Value.ToString(),
19+
PropertyType = GetPropertyType(propCond.Value)
20+
}
21+
};
22+
}
23+
// Add specialized support for And/Or/Not if needed, mostly PropertyCondition is used locally.
24+
// For now, let's assume simple PropertyConditions as seen in the codebase.
25+
26+
throw new NotImplementedException($"Condition type {condition.GetType().Name} is not supported yet.");
27+
}
28+
29+
private static UiAutomation.PropertyType GetPropertyType(object value)
30+
{
31+
if (value is bool) return UiAutomation.PropertyType.Bool;
32+
if (value is int) return UiAutomation.PropertyType.Int;
33+
return UiAutomation.PropertyType.String;
34+
}
35+
}
36+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
6+
7+
namespace UiAutomationGRPC.Client.Framework.Helpers
8+
{
9+
public class DataHelper
10+
{
11+
public static void InitData()
12+
{
13+
14+
}
15+
16+
[MethodImpl(MethodImplOptions.NoInlining)]
17+
public static string GetCurrentMethodName(int frame = 3)
18+
{
19+
var st = new StackTrace();
20+
var sf = st.GetFrame(frame);
21+
var val = sf.GetMethod().Name;
22+
val = string.Concat(val.Select(x => char.IsUpper(x) ? " " + x : x.ToString())).TrimStart(' ');
23+
return val;
24+
}
25+
26+
public static void SetSystemGlobalVariable(string variableName, string variableValue)
27+
{
28+
try
29+
{
30+
Environment.SetEnvironmentVariable(variableName, variableValue, EnvironmentVariableTarget.User);
31+
Console.WriteLine("System global variable " + GetSystemGlobalVariable(variableName));
32+
}
33+
catch
34+
{
35+
Console.WriteLine("Variable not created");
36+
}
37+
// PwerShell scripts
38+
// Get-ChildItem Env:
39+
// Remove-Item Env:\Testname
40+
}
41+
42+
43+
public static string GetSystemGlobalVariable(string variableName)
44+
{
45+
string variableValue = null;
46+
try
47+
{
48+
variableValue = Environment.GetEnvironmentVariable(variableName, EnvironmentVariableTarget.User);
49+
Console.WriteLine("Variable " + variableValue + " created");
50+
}
51+
catch
52+
{
53+
Console.WriteLine("Variable not created");
54+
}
55+
return variableValue;
56+
}
57+
58+
[DllImport("kernel32.dll")]
59+
public static extern void GetSystemTime(ref Systemtime lpSystemTime);
60+
61+
[DllImport("kernel32.dll")]
62+
private static extern uint SetSystemTime(ref Systemtime lpSystemTime);
63+
64+
public struct Systemtime
65+
{
66+
public ushort wYear;
67+
public ushort wMonth;
68+
public ushort wDayOfWeek;
69+
public ushort wDay;
70+
public ushort wHour;
71+
public ushort wMinute;
72+
public ushort wSecond;
73+
public ushort wMilliseconds;
74+
}
75+
}
76+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using CoreTest.Helpers;
2+
using Grpc.Core.Logging;
3+
using UiAutomation;
4+
5+
namespace UiAutomationGRPC.Client.Framework.Helpers
6+
{
7+
public static class KeyboardHelper
8+
{
9+
private static UiAutomationService.UiAutomationServiceClient _client;
10+
11+
public static void Init(UiAutomationService.UiAutomationServiceClient client)
12+
{
13+
_client = client;
14+
}
15+
16+
public static void SendKey(string buttonKey, int count)
17+
{
18+
19+
for (var send = 0; send < count; send++)
20+
{
21+
SendKey(buttonKey);
22+
}
23+
}
24+
25+
public static void SendKey(string buttonKey)
26+
{
27+
System.Threading.Thread.Sleep(UsabilityTimeLimits.KeyboardReadiness);
28+
SendKeyInternal(buttonKey);
29+
}
30+
31+
private static void SendKeyInternal(string buttonKey)
32+
{
33+
if (_client != null)
34+
{
35+
// Synchronous call for now, as SendSendKeys is void in helper usage but async in gRPC
36+
// Using .GetAwaiter().GetResult() to keep method signature if needed, or fire and forget.
37+
// Given SendKey in tests usually expects action completion, we block.
38+
try
39+
{
40+
_client.SendKeys(new UiAutomation.SendKeysRequest { Keys = buttonKey, Wait = true });
41+
Logger.WriteLog(buttonKey + " sent via gRPC");
42+
}
43+
catch (System.Exception ex)
44+
{
45+
Logger.WriteLog($"Failed to send key {buttonKey}: {ex.Message}");
46+
}
47+
}
48+
else
49+
{
50+
Logger.WriteLog(buttonKey + " sent (simulation only, no client)");
51+
}
52+
}
53+
54+
}
55+
}

0 commit comments

Comments
 (0)