appium-novawindows2-driver 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +97 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.yml +33 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
  4. package/.github/workflows/lint-build.yml +30 -0
  5. package/.github/workflows/release.yml +39 -0
  6. package/.releaserc +41 -0
  7. package/CHANGELOG.md +33 -0
  8. package/LICENSE +202 -0
  9. package/README.md +557 -0
  10. package/build/eslint.config.d.mts +3 -0
  11. package/build/eslint.config.d.mts.map +1 -0
  12. package/build/eslint.config.mjs +6 -0
  13. package/build/eslint.config.mjs.map +1 -0
  14. package/build/lib/commands/actions.d.ts +11 -0
  15. package/build/lib/commands/actions.d.ts.map +1 -0
  16. package/build/lib/commands/actions.js +212 -0
  17. package/build/lib/commands/actions.js.map +1 -0
  18. package/build/lib/commands/app.d.ts +12 -0
  19. package/build/lib/commands/app.d.ts.map +1 -0
  20. package/build/lib/commands/app.js +195 -0
  21. package/build/lib/commands/app.js.map +1 -0
  22. package/build/lib/commands/device.d.ts +3 -0
  23. package/build/lib/commands/device.d.ts.map +1 -0
  24. package/build/lib/commands/device.js +31 -0
  25. package/build/lib/commands/device.js.map +1 -0
  26. package/build/lib/commands/element.d.ts +15 -0
  27. package/build/lib/commands/element.d.ts.map +1 -0
  28. package/build/lib/commands/element.js +213 -0
  29. package/build/lib/commands/element.js.map +1 -0
  30. package/build/lib/commands/extension.d.ts +87 -0
  31. package/build/lib/commands/extension.d.ts.map +1 -0
  32. package/build/lib/commands/extension.js +511 -0
  33. package/build/lib/commands/extension.js.map +1 -0
  34. package/build/lib/commands/functions.d.ts +3 -0
  35. package/build/lib/commands/functions.d.ts.map +1 -0
  36. package/build/lib/commands/functions.js +194 -0
  37. package/build/lib/commands/functions.js.map +1 -0
  38. package/build/lib/commands/index.d.ts +126 -0
  39. package/build/lib/commands/index.d.ts.map +1 -0
  40. package/build/lib/commands/index.js +54 -0
  41. package/build/lib/commands/index.js.map +1 -0
  42. package/build/lib/commands/powershell.d.ts +6 -0
  43. package/build/lib/commands/powershell.d.ts.map +1 -0
  44. package/build/lib/commands/powershell.js +204 -0
  45. package/build/lib/commands/powershell.js.map +1 -0
  46. package/build/lib/commands/system.d.ts +4 -0
  47. package/build/lib/commands/system.d.ts.map +1 -0
  48. package/build/lib/commands/system.js +8 -0
  49. package/build/lib/commands/system.js.map +1 -0
  50. package/build/lib/constants.d.ts +2 -0
  51. package/build/lib/constants.d.ts.map +1 -0
  52. package/build/lib/constants.js +5 -0
  53. package/build/lib/constants.js.map +1 -0
  54. package/build/lib/constraints.d.ts +40 -0
  55. package/build/lib/constraints.d.ts.map +1 -0
  56. package/build/lib/constraints.js +42 -0
  57. package/build/lib/constraints.js.map +1 -0
  58. package/build/lib/driver.d.ts +33 -0
  59. package/build/lib/driver.d.ts.map +1 -0
  60. package/build/lib/driver.js +181 -0
  61. package/build/lib/driver.js.map +1 -0
  62. package/build/lib/enums.d.ts +89 -0
  63. package/build/lib/enums.d.ts.map +1 -0
  64. package/build/lib/enums.js +83 -0
  65. package/build/lib/enums.js.map +1 -0
  66. package/build/lib/powershell/common.d.ts +39 -0
  67. package/build/lib/powershell/common.d.ts.map +1 -0
  68. package/build/lib/powershell/common.js +121 -0
  69. package/build/lib/powershell/common.js.map +1 -0
  70. package/build/lib/powershell/conditions.d.ts +24 -0
  71. package/build/lib/powershell/conditions.d.ts.map +1 -0
  72. package/build/lib/powershell/conditions.js +131 -0
  73. package/build/lib/powershell/conditions.js.map +1 -0
  74. package/build/lib/powershell/converter.d.ts +3 -0
  75. package/build/lib/powershell/converter.d.ts.map +1 -0
  76. package/build/lib/powershell/converter.js +273 -0
  77. package/build/lib/powershell/converter.js.map +1 -0
  78. package/build/lib/powershell/core.d.ts +8 -0
  79. package/build/lib/powershell/core.d.ts.map +1 -0
  80. package/build/lib/powershell/core.js +30 -0
  81. package/build/lib/powershell/core.js.map +1 -0
  82. package/build/lib/powershell/elements.d.ts +68 -0
  83. package/build/lib/powershell/elements.d.ts.map +1 -0
  84. package/build/lib/powershell/elements.js +515 -0
  85. package/build/lib/powershell/elements.js.map +1 -0
  86. package/build/lib/powershell/index.d.ts +8 -0
  87. package/build/lib/powershell/index.d.ts.map +1 -0
  88. package/build/lib/powershell/index.js +24 -0
  89. package/build/lib/powershell/index.js.map +1 -0
  90. package/build/lib/powershell/regex.d.ts +19 -0
  91. package/build/lib/powershell/regex.d.ts.map +1 -0
  92. package/build/lib/powershell/regex.js +68 -0
  93. package/build/lib/powershell/regex.js.map +1 -0
  94. package/build/lib/powershell/types.d.ts +155 -0
  95. package/build/lib/powershell/types.d.ts.map +1 -0
  96. package/build/lib/powershell/types.js +141 -0
  97. package/build/lib/powershell/types.js.map +1 -0
  98. package/build/lib/util.d.ts +10 -0
  99. package/build/lib/util.d.ts.map +1 -0
  100. package/build/lib/util.js +51 -0
  101. package/build/lib/util.js.map +1 -0
  102. package/build/lib/winapi/types/index.d.ts +8 -0
  103. package/build/lib/winapi/types/index.d.ts.map +1 -0
  104. package/build/lib/winapi/types/index.js +24 -0
  105. package/build/lib/winapi/types/index.js.map +1 -0
  106. package/build/lib/winapi/types/input.d.ts +11 -0
  107. package/build/lib/winapi/types/input.d.ts.map +1 -0
  108. package/build/lib/winapi/types/input.js +12 -0
  109. package/build/lib/winapi/types/input.js.map +1 -0
  110. package/build/lib/winapi/types/keyeventf.d.ts +13 -0
  111. package/build/lib/winapi/types/keyeventf.d.ts.map +1 -0
  112. package/build/lib/winapi/types/keyeventf.js +14 -0
  113. package/build/lib/winapi/types/keyeventf.js.map +1 -0
  114. package/build/lib/winapi/types/mouseeventf.d.ts +34 -0
  115. package/build/lib/winapi/types/mouseeventf.d.ts.map +1 -0
  116. package/build/lib/winapi/types/mouseeventf.js +37 -0
  117. package/build/lib/winapi/types/mouseeventf.js.map +1 -0
  118. package/build/lib/winapi/types/scancode.d.ts +95 -0
  119. package/build/lib/winapi/types/scancode.d.ts.map +1 -0
  120. package/build/lib/winapi/types/scancode.js +96 -0
  121. package/build/lib/winapi/types/scancode.js.map +1 -0
  122. package/build/lib/winapi/types/systemmetric.d.ts +214 -0
  123. package/build/lib/winapi/types/systemmetric.d.ts.map +1 -0
  124. package/build/lib/winapi/types/systemmetric.js +215 -0
  125. package/build/lib/winapi/types/systemmetric.js.map +1 -0
  126. package/build/lib/winapi/types/virtualkey.d.ts +353 -0
  127. package/build/lib/winapi/types/virtualkey.d.ts.map +1 -0
  128. package/build/lib/winapi/types/virtualkey.js +354 -0
  129. package/build/lib/winapi/types/virtualkey.js.map +1 -0
  130. package/build/lib/winapi/types/xmousebutton.d.ts +7 -0
  131. package/build/lib/winapi/types/xmousebutton.d.ts.map +1 -0
  132. package/build/lib/winapi/types/xmousebutton.js +8 -0
  133. package/build/lib/winapi/types/xmousebutton.js.map +1 -0
  134. package/build/lib/winapi/user32.d.ts +56 -0
  135. package/build/lib/winapi/user32.d.ts.map +1 -0
  136. package/build/lib/winapi/user32.js +591 -0
  137. package/build/lib/winapi/user32.js.map +1 -0
  138. package/build/lib/xpath/core.d.ts +8 -0
  139. package/build/lib/xpath/core.d.ts.map +1 -0
  140. package/build/lib/xpath/core.js +593 -0
  141. package/build/lib/xpath/core.js.map +1 -0
  142. package/build/lib/xpath/functions.d.ts +4 -0
  143. package/build/lib/xpath/functions.d.ts.map +1 -0
  144. package/build/lib/xpath/functions.js +271 -0
  145. package/build/lib/xpath/functions.js.map +1 -0
  146. package/build/lib/xpath/index.d.ts +3 -0
  147. package/build/lib/xpath/index.d.ts.map +1 -0
  148. package/build/lib/xpath/index.js +19 -0
  149. package/build/lib/xpath/index.js.map +1 -0
  150. package/eslint.config.mjs +11 -0
  151. package/examples/C#/CalculatorTest/CalculatorTest/CalculatorSession.cs +44 -0
  152. package/examples/C#/CalculatorTest/CalculatorTest/CalculatorTest.csproj +24 -0
  153. package/examples/C#/CalculatorTest/CalculatorTest/ScenarioStandard.cs +121 -0
  154. package/examples/C#/CalculatorTest/CalculatorTest/ScenarioStandardInvoke.cs +121 -0
  155. package/examples/C#/CalculatorTest/CalculatorTest.sln +16 -0
  156. package/lib/commands/actions.ts +229 -0
  157. package/lib/commands/app.ts +227 -0
  158. package/lib/commands/device.ts +41 -0
  159. package/lib/commands/element.ts +242 -0
  160. package/lib/commands/extension.ts +636 -0
  161. package/lib/commands/functions.ts +192 -0
  162. package/lib/commands/index.ts +28 -0
  163. package/lib/commands/powershell.ts +243 -0
  164. package/lib/commands/system.ts +7 -0
  165. package/lib/constants.ts +1 -0
  166. package/lib/constraints.ts +43 -0
  167. package/lib/driver.ts +247 -0
  168. package/lib/enums.ts +96 -0
  169. package/lib/powershell/common.ts +137 -0
  170. package/lib/powershell/conditions.ts +169 -0
  171. package/lib/powershell/converter.ts +373 -0
  172. package/lib/powershell/core.ts +29 -0
  173. package/lib/powershell/elements.ts +584 -0
  174. package/lib/powershell/index.ts +7 -0
  175. package/lib/powershell/regex.ts +77 -0
  176. package/lib/powershell/types.ts +208 -0
  177. package/lib/util.ts +52 -0
  178. package/lib/winapi/types/index.ts +7 -0
  179. package/lib/winapi/types/input.ts +12 -0
  180. package/lib/winapi/types/keyeventf.ts +14 -0
  181. package/lib/winapi/types/mouseeventf.ts +37 -0
  182. package/lib/winapi/types/scancode.ts +96 -0
  183. package/lib/winapi/types/systemmetric.ts +215 -0
  184. package/lib/winapi/types/virtualkey.ts +354 -0
  185. package/lib/winapi/types/xmousebutton.ts +8 -0
  186. package/lib/winapi/user32.ts +842 -0
  187. package/lib/xpath/core.ts +699 -0
  188. package/lib/xpath/functions.ts +366 -0
  189. package/lib/xpath/index.ts +2 -0
  190. package/package.json +61 -0
  191. package/tsconfig.json +13 -0
  192. package/verify_driver.js +96 -0
@@ -0,0 +1,121 @@
1
+ // Based on the original WinAppDriver Calculator test by Microsoft, licensed under the MIT License.
2
+
3
+ using OpenQA.Selenium;
4
+ using OpenQA.Selenium.Appium;
5
+
6
+ namespace CalculatorTest;
7
+
8
+ public class ScenarioStandardInvoke : CalculatorSession
9
+ {
10
+ private static AppiumElement _header;
11
+ private static AppiumElement _calculatorResult;
12
+
13
+ [Test]
14
+ public void Addition()
15
+ {
16
+ // Find the buttons by their names and click them in sequence to perform 1 + 7 = 8
17
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.Name("One")));
18
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.Name("Plus")));
19
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.Name("Seven")));
20
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.Name("Equals")));
21
+ Assert.That(GetCalculatorResultText(), Is.EqualTo("8"));
22
+ }
23
+
24
+ [Test]
25
+ public void Division()
26
+ {
27
+ // Find the buttons by their accessibility ids and click them in sequence to perform 88 / 11 = 8
28
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.AccessibilityId("num8Button")));
29
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.AccessibilityId("num8Button")));
30
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.AccessibilityId("divideButton")));
31
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.AccessibilityId("num1Button")));
32
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.AccessibilityId("num1Button")));
33
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.AccessibilityId("equalButton")));
34
+ Assert.That(GetCalculatorResultText(), Is.EqualTo("8"));
35
+ }
36
+
37
+ [Test]
38
+ public void Multiplication()
39
+ {
40
+ // Find the buttons by their names using XPath and click them in sequence to perform 9 x 9 = 81
41
+ Session.ExecuteScript("windows: invoke", Session.FindElement(By.XPath("//Button[@Name='Nine']")));
42
+ Session.ExecuteScript("windows: invoke", Session.FindElement(By.XPath("//Button[@Name='Multiply by']")));
43
+ Session.ExecuteScript("windows: invoke", Session.FindElement(By.XPath("//Button[@Name='Nine']")));
44
+ Session.ExecuteScript("windows: invoke", Session.FindElement(By.XPath("//Button[@Name='Equals']")));
45
+ Assert.That(GetCalculatorResultText(), Is.EqualTo("81"));
46
+ }
47
+
48
+ [Test]
49
+ public void Subtraction()
50
+ {
51
+ // Find the buttons by their accessibility ids using XPath and click them in sequence to perform 9 - 1 = 8
52
+ Session.ExecuteScript("windows: invoke", Session.FindElement(By.XPath("//Button[@AutomationId=\"num9Button\"]")));
53
+ Session.ExecuteScript("windows: invoke", Session.FindElement(By.XPath("//Button[@AutomationId=\"minusButton\"]")));
54
+ Session.ExecuteScript("windows: invoke", Session.FindElement(By.XPath("//Button[@AutomationId=\"num1Button\"]")));
55
+ Session.ExecuteScript("windows: invoke", Session.FindElement(By.XPath("//Button[@AutomationId=\"equalButton\"]")));
56
+ Assert.That(GetCalculatorResultText(), Is.EqualTo("8"));
57
+ }
58
+
59
+ [TestCase("One", "Plus", "Seven", "8")]
60
+ [TestCase("Nine", "Minus", "One", "8")]
61
+ [TestCase("Eight", "Divide by", "Eight", "1")]
62
+ public void Templatized(string input1, string operation, string input2, string expectedResult)
63
+ {
64
+ // Run sequence of button presses specified above and validate the results
65
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.Name(input1)));
66
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.Name(operation)));
67
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.Name(input2)));
68
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.Name("Equals")));
69
+ Assert.That(GetCalculatorResultText(), Is.EqualTo(expectedResult));
70
+ }
71
+
72
+ [OneTimeSetUp]
73
+ public static void ClassInitialize()
74
+ {
75
+ // Create session to launch a Calculator window
76
+ Setup();
77
+
78
+ // Identify calculator mode by locating the header
79
+ try
80
+ {
81
+ _header = Session.FindElement(MobileBy.AccessibilityId("Header"));
82
+ }
83
+ catch
84
+ {
85
+ _header = Session.FindElement(MobileBy.AccessibilityId("ContentPresenter"));
86
+ }
87
+
88
+ // Ensure that calculator is in standard mode
89
+ if (!_header.Text.Equals("Standard", StringComparison.OrdinalIgnoreCase))
90
+ {
91
+ Session.ExecuteScript("windows: invoke", Session.FindElement(MobileBy.AccessibilityId("TogglePaneButton")));
92
+ Thread.Sleep(TimeSpan.FromSeconds(1));
93
+ var splitViewPane = Session.FindElement(MobileBy.ClassName("SplitViewPane"));
94
+ Session.ExecuteScript("windows: invoke", splitViewPane.FindElement(MobileBy.Name("Standard Calculator")));
95
+ Thread.Sleep(TimeSpan.FromSeconds(1));
96
+ Assert.That(_header.Text.Equals("Standard", StringComparison.OrdinalIgnoreCase), Is.True);
97
+ }
98
+
99
+ // Locate the calculatorResult element
100
+ _calculatorResult = Session.FindElement(MobileBy.AccessibilityId("CalculatorResults"));
101
+ Assert.That(_calculatorResult, Is.Not.Null);
102
+ }
103
+
104
+ [OneTimeTearDown]
105
+ public static void ClassCleanup()
106
+ {
107
+ TearDown();
108
+ }
109
+
110
+ [SetUp]
111
+ public void Clear()
112
+ {
113
+ Session.ExecuteScript("windows: invoke", Session?.FindElement(MobileBy.Name("Clear")));
114
+ Assert.That(GetCalculatorResultText(), Is.EqualTo("0"));
115
+ }
116
+
117
+ private static string GetCalculatorResultText()
118
+ {
119
+ return _calculatorResult.Text.Replace("Display is", string.Empty).Trim();
120
+ }
121
+ }
@@ -0,0 +1,16 @@
1
+ 
2
+ Microsoft Visual Studio Solution File, Format Version 12.00
3
+ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CalculatorTest", "CalculatorTest\CalculatorTest.csproj", "{AAB0751F-0906-4FE2-8725-BF90D7AC9F9D}"
4
+ EndProject
5
+ Global
6
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
7
+ Debug|Any CPU = Debug|Any CPU
8
+ Release|Any CPU = Release|Any CPU
9
+ EndGlobalSection
10
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
11
+ {AAB0751F-0906-4FE2-8725-BF90D7AC9F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12
+ {AAB0751F-0906-4FE2-8725-BF90D7AC9F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
13
+ {AAB0751F-0906-4FE2-8725-BF90D7AC9F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
14
+ {AAB0751F-0906-4FE2-8725-BF90D7AC9F9D}.Release|Any CPU.Build.0 = Release|Any CPU
15
+ EndGlobalSection
16
+ EndGlobal
@@ -0,0 +1,229 @@
1
+ import {
2
+ ActionSequence,
3
+ KeyAction,
4
+ KeyActionSequence,
5
+ NullActionSequence,
6
+ PointerActionSequence,
7
+ PointerMoveAction,
8
+ Rect,
9
+ ScrollAction,
10
+ WheelActionSequence,
11
+ } from '@appium/types';
12
+
13
+ import { W3C_ELEMENT_KEY, errors } from '@appium/base-driver';
14
+ import { NovaWindowsDriver } from '../driver';
15
+ import { keyDown, keyUp, mouseMoveRelative, mouseMoveAbsolute, mouseDown, mouseUp, mouseScroll } from '../winapi/user32';
16
+ import { sleep } from '../util';
17
+ import { AutomationElement, FoundAutomationElement } from '../powershell';
18
+ import { Key } from '../enums';
19
+
20
+ export async function performActions(this: NovaWindowsDriver, actionSequences: ActionSequence[]): Promise<void> {
21
+ for (const actionSequence of actionSequences) {
22
+ switch (actionSequence.type) {
23
+ case 'key':
24
+ await this.handleKeyActionSequence(actionSequence);
25
+ break;
26
+ case 'wheel':
27
+ await this.handleWheelActionSequence(actionSequence);
28
+ break;
29
+ case 'pointer':
30
+ await this.handlePointerActionSequence(actionSequence);
31
+ break;
32
+ case 'none':
33
+ await this.handleNullActionSequence(actionSequence);
34
+ break;
35
+ default:
36
+ throw new errors.InvalidArgumentError();
37
+ }
38
+ }
39
+ };
40
+
41
+ export async function handleKeyActionSequence(this: NovaWindowsDriver, actionSequence: KeyActionSequence): Promise<void> {
42
+ const actions = actionSequence.actions;
43
+ for (const action of actions) {
44
+ await this.handleKeyAction(action);
45
+ }
46
+ }
47
+
48
+ export async function handlePointerActionSequence(this: NovaWindowsDriver, actionSequence: PointerActionSequence): Promise<void> {
49
+ switch (actionSequence.parameters?.pointerType) {
50
+ case 'touch':
51
+ case 'pen':
52
+ throw new errors.NotImplementedError(`Pointer type ${actionSequence.parameters?.pointerType} not implemented yet.`);
53
+ case 'mouse':
54
+ default:
55
+ await this.handleMousePointerActionSequence(actionSequence);
56
+ }
57
+ }
58
+
59
+ export async function handleMousePointerActionSequence(this: NovaWindowsDriver, actionSequence: PointerActionSequence): Promise<void> {
60
+ const actions = actionSequence.actions;
61
+ for (const action of actions) {
62
+ switch (action.type) {
63
+ case 'pointerMove':
64
+ await this.handleMouseMoveAction(action);
65
+ break;
66
+ case 'pointerDown':
67
+ mouseDown(action.button);
68
+ break;
69
+ case 'pointerUp':
70
+ mouseUp(action.button);
71
+ break;
72
+ case 'pause':
73
+ if (action.duration) {
74
+ await sleep(action.duration);
75
+ }
76
+ break;
77
+ default:
78
+ throw new errors.InvalidArgumentError();
79
+ }
80
+ }
81
+ }
82
+
83
+ export async function handleWheelActionSequence(this: NovaWindowsDriver, actionSequence: WheelActionSequence): Promise<void> {
84
+ const actions = actionSequence.actions;
85
+ for (const action of actions) {
86
+ switch (action.type) {
87
+ case 'scroll':
88
+ await this.handleMouseMoveAction(action);
89
+ mouseScroll(action.deltaX, action.deltaY);
90
+ break;
91
+ case 'pause':
92
+ if (action.duration) {
93
+ await sleep(action.duration);
94
+ }
95
+ break;
96
+ default:
97
+ throw new errors.InvalidArgumentError();
98
+ }
99
+ }
100
+ }
101
+
102
+ export async function handleNullActionSequence(this: NovaWindowsDriver, actionSequence: NullActionSequence): Promise<void> {
103
+ const actions = actionSequence.actions;
104
+ for (const action of actions) {
105
+ if (action.duration) {
106
+ await sleep(action.duration);
107
+ }
108
+ }
109
+ }
110
+
111
+ export async function handleMouseMoveAction(this: NovaWindowsDriver, action: PointerMoveAction | ScrollAction): Promise<void> {
112
+ const easingFunction = this.caps.smoothPointerMove;
113
+ switch (action.origin) {
114
+ case 'pointer':
115
+ await mouseMoveRelative(action.x, action.y, action.duration, easingFunction);
116
+ break;
117
+ case 'viewport': {
118
+ const rootRectJson = await this.sendPowerShellCommand(AutomationElement.automationRoot.buildGetElementRectCommand());
119
+ const rootRect = JSON.parse(rootRectJson.replaceAll(/(?:infinity)/gi, 0x7FFFFFFF.toString())) as Rect;
120
+ await mouseMoveAbsolute(action.x + rootRect.x, action.y + rootRect.y, action.duration, easingFunction);
121
+ break;
122
+ }
123
+ default:
124
+ if (action.origin?.[W3C_ELEMENT_KEY]) {
125
+ const element = new FoundAutomationElement(action.origin[W3C_ELEMENT_KEY]);
126
+ const rectJson = await this.sendPowerShellCommand(element.buildGetElementRectCommand());
127
+ let rect = JSON.parse(rectJson.replaceAll(/(?:infinity)/gi, 0x7FFFFFFF.toString())) as Rect;
128
+
129
+ if (Object.values(rect).some((x) => x === 0x7FFFFFFF)) {
130
+ await this.sendPowerShellCommand(element.buildScrollIntoViewCommand());
131
+ rect = JSON.parse(rectJson.replaceAll(/(?:infinity)/gi, 0x7FFFFFFF.toString())) as Rect;
132
+ }
133
+
134
+ await mouseMoveAbsolute(action.x === 0 ? rect.x + rect.width / 2 : action.x, action.y === 0 ? rect.y + rect.height / 2 : action.y, action.duration, easingFunction);
135
+ break;
136
+ }
137
+
138
+ throw new errors.InvalidArgumentError();
139
+ }
140
+ }
141
+
142
+ export async function handleKeyAction(this: NovaWindowsDriver, action: KeyAction): Promise<void> {
143
+ if (action.type === 'pause') {
144
+ if (action.duration) {
145
+ await sleep(action.duration);
146
+ }
147
+ return;
148
+ }
149
+
150
+ switch (action.value) {
151
+ case Key.SHIFT:
152
+ case Key.R_SHIFT:
153
+ if (action.type === 'keyDown') {
154
+ keyDown(action.value);
155
+ this.keyboardState.shift = true;
156
+ return;
157
+ }
158
+
159
+ keyUp(Key.SHIFT);
160
+ keyUp(Key.R_SHIFT);
161
+ this.keyboardState.shift = false;
162
+ return;
163
+ case Key.CONTROL:
164
+ case Key.R_CONTROL:
165
+ if (action.type === 'keyDown') {
166
+ keyDown(action.value);
167
+ this.keyboardState.ctrl = true;
168
+ return;
169
+ }
170
+
171
+ keyUp(Key.CONTROL);
172
+ keyUp(Key.R_CONTROL);
173
+ this.keyboardState.ctrl = false;
174
+ return;
175
+ case Key.META:
176
+ case Key.R_META:
177
+ if (action.type === 'keyDown') {
178
+ keyDown(action.value);
179
+ this.keyboardState.meta = true;
180
+ return;
181
+ }
182
+
183
+ keyUp(Key.META);
184
+ keyUp(Key.R_META);
185
+ this.keyboardState.meta = false;
186
+ return;
187
+ case Key.ALT:
188
+ case Key.R_ALT:
189
+ if (action.type === 'keyDown') {
190
+ keyDown(action.value);
191
+ this.keyboardState.alt = true;
192
+ return;
193
+ }
194
+
195
+ keyUp(Key.ALT);
196
+ keyUp(Key.R_ALT);
197
+ this.keyboardState.alt = false;
198
+ return;
199
+ case Key.NULL:
200
+ if (action.type === 'keyDown') {
201
+ if (this.keyboardState.shift) {
202
+ await this.handleKeyAction({ type: 'keyUp', value: Key.SHIFT });
203
+ }
204
+ if (this.keyboardState.ctrl) {
205
+ await this.handleKeyAction({ type: 'keyUp', value: Key.CONTROL });
206
+ }
207
+ if (this.keyboardState.meta) {
208
+ await this.handleKeyAction({ type: 'keyUp', value: Key.META });
209
+ }
210
+ if (this.keyboardState.alt) {
211
+ await this.handleKeyAction({ type: 'keyUp', value: Key.ALT });
212
+ }
213
+ for (const key in Array.of(this.keyboardState.pressed)) {
214
+ keyUp(key);
215
+ this.keyboardState.pressed.delete(key);
216
+ }
217
+ }
218
+ return;
219
+ default:
220
+ if (action.type === 'keyDown') {
221
+ keyDown(action.value);
222
+ this.keyboardState.pressed.add(action.value);
223
+ }
224
+ else {
225
+ keyUp(action.value);
226
+ this.keyboardState.pressed.delete(action.value);
227
+ }
228
+ }
229
+ }
@@ -0,0 +1,227 @@
1
+ import { normalize } from 'node:path';
2
+ import { Element, Rect } from '@appium/types';
3
+ import { NovaWindowsDriver } from '../driver';
4
+ import {
5
+ AutomationElement,
6
+ FoundAutomationElement,
7
+ PSInt32,
8
+ PSString,
9
+ Property,
10
+ PropertyCondition,
11
+ TreeScope,
12
+ TrueCondition,
13
+ pwsh$,
14
+ pwsh,
15
+ } from '../powershell';
16
+ import { sleep } from '../util';
17
+ import { errors, W3C_ELEMENT_KEY } from '@appium/base-driver';
18
+ import {
19
+ getWindowAllHandlesForProcessIds,
20
+ trySetForegroundWindow,
21
+ } from '../winapi/user32';
22
+
23
+ const GET_PAGE_SOURCE_COMMAND = pwsh$ /* ps1 */ `
24
+ $el = ${0}
25
+
26
+ if ($el -eq $null) {
27
+ $dummy = [xml]'<DummyRoot></DummyRoot>'
28
+ return $dummy.OuterXml
29
+ }
30
+
31
+ Get-PageSource $el |
32
+ ForEach-Object { $_.OuterXml }
33
+ `;
34
+
35
+ const GET_SCREENSHOT_COMMAND = pwsh /* ps1 */ `
36
+ if ($rootElement -eq $null) {
37
+ $bitmap = New-Object Drawing.Bitmap 1,1
38
+ $stream = New-Object IO.MemoryStream
39
+ $bitmap.Save($stream, [Drawing.Imaging.ImageFormat]::Png)
40
+ $bitmap.Dispose()
41
+ return [Convert]::ToBase64String($stream.ToArray())
42
+ }
43
+
44
+ $rect = $rootElement.Current.BoundingRectangle
45
+ $bitmap = New-Object Drawing.Bitmap([int32]$rect.Width, [int32]$rect.Height)
46
+
47
+ $graphics = [Drawing.Graphics]::FromImage($bitmap)
48
+ $graphics.CopyFromScreen([int32]$rect.Left, [int32]$rect.Top, 0, 0, $bitmap.Size)
49
+ $graphics.Dispose()
50
+
51
+ $stream = New-Object IO.MemoryStream
52
+ $bitmap.Save($stream, [Drawing.Imaging.ImageFormat]::Png)
53
+ $bitmap.Dispose()
54
+
55
+ [Convert]::ToBase64String($stream.ToArray())
56
+ `;
57
+
58
+ export async function getPageSource(this: NovaWindowsDriver): Promise<string> {
59
+ return await this.sendPowerShellCommand(GET_PAGE_SOURCE_COMMAND.format(AutomationElement.automationRoot));
60
+ }
61
+
62
+ export async function getScreenshot(this: NovaWindowsDriver): Promise<string> {
63
+ const automationRootId = await this.sendPowerShellCommand(AutomationElement.automationRoot.buildCommand());
64
+
65
+ if (this.caps.app && this.caps.app.toLowerCase() !== 'root') {
66
+ try {
67
+ await this.focusElement({
68
+ [W3C_ELEMENT_KEY]: automationRootId.trim(),
69
+ } satisfies Element);
70
+ } catch {
71
+ // noop
72
+ }
73
+ }
74
+
75
+ return await this.sendPowerShellCommand(GET_SCREENSHOT_COMMAND);
76
+ }
77
+
78
+ export async function getWindowRect(this: NovaWindowsDriver): Promise<Rect> {
79
+ const result = await this.sendPowerShellCommand(AutomationElement.automationRoot.buildGetElementRectCommand());
80
+ return JSON.parse(result.replaceAll(/(?:infinity)/gi, 0x7FFFFFFF.toString()));
81
+ }
82
+
83
+ export async function getWindowHandle(this: NovaWindowsDriver): Promise<string> {
84
+ const nativeWindowHandle = await this.sendPowerShellCommand(AutomationElement.automationRoot.buildGetPropertyCommand(Property.NATIVE_WINDOW_HANDLE));
85
+ return `0x${Number(nativeWindowHandle).toString(16).padStart(8, '0')}`;
86
+ }
87
+
88
+ export async function getWindowHandles(this: NovaWindowsDriver): Promise<string[]> {
89
+ const result = await this.sendPowerShellCommand(AutomationElement.rootElement.findAll(TreeScope.CHILDREN, new TrueCondition()).buildCommand());
90
+ const elIds = result.split('\n').map((x) => x.trim()).filter(Boolean);
91
+ const nativeWindowHandles: string[] = [];
92
+
93
+ for (const elId of elIds) {
94
+ const nativeWindowHandle = await this.sendPowerShellCommand(new FoundAutomationElement(elId).buildGetPropertyCommand(Property.NATIVE_WINDOW_HANDLE));
95
+ nativeWindowHandles.push(`0x${Number(nativeWindowHandle).toString(16).padStart(8, '0')}`);
96
+ }
97
+
98
+ return nativeWindowHandles;
99
+ }
100
+
101
+ export async function setWindow(this: NovaWindowsDriver, nameOrHandle: string): Promise<void> {
102
+ const handle = Number(nameOrHandle);
103
+ for (let i = 1; i <= 20; i++) { // TODO: make a setting for the number of retries or timeout
104
+ if (!isNaN(handle)) {
105
+ const condition = new PropertyCondition(Property.NATIVE_WINDOW_HANDLE, new PSInt32(handle));
106
+ const elementId = await this.sendPowerShellCommand(AutomationElement.rootElement.findFirst(TreeScope.CHILDREN_OR_SELF, condition).buildCommand());
107
+
108
+ if (elementId.trim() !== '') {
109
+ await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new FoundAutomationElement(elementId).buildCommand()}`);
110
+ trySetForegroundWindow(handle);
111
+ return;
112
+ }
113
+ }
114
+
115
+ const name = nameOrHandle;
116
+ const condition = new PropertyCondition(Property.NAME, new PSString(name));
117
+ const elementId = await this.sendPowerShellCommand(AutomationElement.rootElement.findFirst(TreeScope.CHILDREN, condition).buildCommand());
118
+
119
+ if (elementId.trim() !== '') {
120
+ this.log.info(`Found window with name '${name}'. Setting it as the root element.`);
121
+ await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new FoundAutomationElement(elementId).buildCommand()}`);
122
+ trySetForegroundWindow(handle);
123
+ return;
124
+ }
125
+
126
+ this.log.info(`Failed to locate window with name '${name}'. Sleeping for 500 milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
127
+ await sleep(500); // TODO: make a setting for the sleep timeout
128
+ }
129
+
130
+ throw new errors.NoSuchWindowError(`No window was found with name or handle '${nameOrHandle}'.`);
131
+ }
132
+
133
+ export async function changeRootElement(this: NovaWindowsDriver, path: string): Promise<void>
134
+ export async function changeRootElement(this: NovaWindowsDriver, nativeWindowHandle: number): Promise<void>
135
+ export async function changeRootElement(this: NovaWindowsDriver, pathOrNativeWindowHandle: string | number): Promise<void> {
136
+ if (typeof pathOrNativeWindowHandle === 'number') {
137
+ const nativeWindowHandle = pathOrNativeWindowHandle;
138
+ const condition = new PropertyCondition(Property.NATIVE_WINDOW_HANDLE, new PSInt32(nativeWindowHandle));
139
+ const elementId = await this.sendPowerShellCommand(AutomationElement.rootElement.findFirst(TreeScope.CHILDREN_OR_SELF, condition).buildCommand());
140
+
141
+ if (elementId.trim() !== '') {
142
+ await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new FoundAutomationElement(elementId).buildCommand()}`);
143
+ trySetForegroundWindow(nativeWindowHandle);
144
+ return;
145
+ }
146
+
147
+ throw new errors.UnknownError('Failed to locate top level window with that window handle.');
148
+ }
149
+
150
+
151
+ const path = pathOrNativeWindowHandle;
152
+ if (path.includes('!') && path.includes('_') && !(path.includes('/') || path.includes('\\'))) {
153
+ this.log.debug('Detected app path to be in the UWP format.');
154
+ await this.sendPowerShellCommand(/* ps1 */ `Start-Process 'explorer.exe' 'shell:AppsFolder\\${path}'${this.caps.appArguments ? ` -ArgumentList '${this.caps.appArguments}'` : ''}`);
155
+ await sleep(500); // TODO: make a setting for the initial wait time
156
+ for (let i = 1; i <= 20; i++) {
157
+ const result = await this.sendPowerShellCommand(/* ps1 */ `(Get-Process -Name 'ApplicationFrameHost').Id`);
158
+ const processIds = result.split('\n').map((pid) => pid.trim()).filter(Boolean).map(Number);
159
+
160
+ this.log.debug('Process IDs of ApplicationFrameHost processes: ' + processIds.join(', '));
161
+ try {
162
+ await this.attachToApplicationWindow(processIds);
163
+ return;
164
+ } catch {
165
+ // noop
166
+ }
167
+
168
+ this.log.info(`Failed to locate window of the app. Sleeping for 500 milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
169
+ await sleep(500); // TODO: make a setting for the sleep timeout
170
+ }
171
+ } else {
172
+ this.log.debug('Detected app path to be in the classic format.');
173
+ const normalizedPath = normalize(path);
174
+ await this.sendPowerShellCommand(/* ps1 */ `Start-Process '${normalizedPath}'${this.caps.appArguments ? ` -ArgumentList '${this.caps.appArguments}'` : ''}`);
175
+ await sleep(500); // TODO: make a setting for the initial wait time
176
+ for (let i = 1; i <= 20; i++) {
177
+ try {
178
+ const breadcrumbs = normalizedPath.toLowerCase().split('\\').flatMap((x) => x.split('/'));
179
+ const executable = breadcrumbs[breadcrumbs.length - 1];
180
+ const processName = executable.endsWith('.exe') ? executable.slice(0, executable.length - 4) : executable;
181
+ const result = await this.sendPowerShellCommand(/* ps1 */ `(Get-Process -Name '${processName}' | Sort-Object StartTime -Descending).Id`);
182
+ const processIds = result.split('\n').map((pid) => pid.trim()).filter(Boolean).map(Number);
183
+ this.log.debug(`Process IDs of '${processName}' processes: ` + processIds.join(', '));
184
+
185
+ await this.attachToApplicationWindow(processIds);
186
+ return;
187
+ } catch (err) {
188
+ if (err instanceof Error) {
189
+ this.log.debug(`Received error:\n${err.message}`);
190
+ }
191
+ }
192
+
193
+ this.log.info(`Failed to locate window of the app. Sleeping for 500 milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
194
+ await sleep(500); // TODO: make a setting for the sleep timeout
195
+ }
196
+ }
197
+
198
+ throw new errors.UnknownError('Failed to locate window of the app.');
199
+ }
200
+
201
+ export async function attachToApplicationWindow(this: NovaWindowsDriver, processIds: number[]): Promise<void> {
202
+ const nativeWindowHandles = getWindowAllHandlesForProcessIds(processIds);
203
+ this.log.debug(`Detected the following native window handles for the given process IDs: ${nativeWindowHandles.map((handle) => `0x${handle.toString(16).padStart(8, '0')}`).join(', ')}`);
204
+
205
+ if (nativeWindowHandles.length !== 0) {
206
+ let elementId = '';
207
+ for (let i = 1; i <= 20; i++) {
208
+ elementId = await this.sendPowerShellCommand(AutomationElement.rootElement.findFirst(TreeScope.CHILDREN, new PropertyCondition(Property.NATIVE_WINDOW_HANDLE, new PSInt32(nativeWindowHandles[0]))).buildCommand());
209
+ if (elementId) {
210
+ break;
211
+ }
212
+ this.log.info(`The window with handle 0x${nativeWindowHandles[0].toString(16).padStart(8, '0')} is not yet available in the UI Automation tree. Sleeping for 500 milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
213
+ await sleep(500); // TODO: make a setting for the sleep timeout
214
+ }
215
+
216
+ await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new FoundAutomationElement(elementId).buildCommand()}`);
217
+ if ((await this.sendPowerShellCommand(/* ps1 */ `$null -ne $rootElement`)).toLowerCase() === 'true') {
218
+ const nativeWindowHandle = Number(await this.sendPowerShellCommand(AutomationElement.automationRoot.buildGetPropertyCommand(Property.NATIVE_WINDOW_HANDLE)));
219
+ if (!trySetForegroundWindow(nativeWindowHandle)) {
220
+ await this.focusElement({
221
+ [W3C_ELEMENT_KEY]: elementId,
222
+ } satisfies Element);
223
+ };
224
+ return;
225
+ }
226
+ }
227
+ }
@@ -0,0 +1,41 @@
1
+ import { NovaWindowsDriver } from '../driver';
2
+ import { PSString, pwsh$ } from '../powershell';
3
+
4
+ const GET_SYSTEM_TIME_COMMAND = pwsh$ /* ps1 */ `(Get-Date).ToString(${0})`;
5
+ const ISO_8061_FORMAT = 'yyyy-MM-ddTHH:mm:sszzz';
6
+
7
+ export async function getDeviceTime(this: NovaWindowsDriver, format?: string): Promise<string> {
8
+ format = format ? new PSString(format).toString() : `'${ISO_8061_FORMAT}'`;
9
+ return await this.sendPowerShellCommand(GET_SYSTEM_TIME_COMMAND.format(format));
10
+ }
11
+
12
+ // command: 'hideKeyboard'
13
+ // payloadParams: { optional: ['strategy', 'key', 'keyCode', 'keyName'] }
14
+
15
+ // command: 'isKeyboardShown'
16
+
17
+ // command: 'pushFile'
18
+ // payloadParams: { required: ['path', 'data'] }
19
+
20
+ // command: 'pullFile'
21
+ // payloadParams: { required: ['path'] }
22
+
23
+ // command: 'pullFolder'
24
+ // payloadParams: { required: ['path'] }
25
+
26
+ // # APP MANAGEMENT
27
+
28
+ // command: 'activateApp'
29
+ // payloadParams: { required: [['appId'], ['bundleId']], optional: ['options'] }
30
+
31
+ // command: 'removeApp'
32
+ // payloadParams: { required: [['appId'], ['bundleId']], optional: ['options'] }
33
+
34
+ //command: 'terminateApp'
35
+ // payloadParams: { required: [['appId'], ['bundleId']], optional: ['options'] }
36
+
37
+ // command: 'isAppInstalled'
38
+ // payloadParams: { required: [['appId'], ['bundleId']] }
39
+
40
+ // command: 'installApp'
41
+ // payloadParams: { required: ['appPath'], optional: ['options'] }