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.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +97 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
- package/.github/workflows/lint-build.yml +30 -0
- package/.github/workflows/release.yml +39 -0
- package/.releaserc +41 -0
- package/CHANGELOG.md +33 -0
- package/LICENSE +202 -0
- package/README.md +557 -0
- package/build/eslint.config.d.mts +3 -0
- package/build/eslint.config.d.mts.map +1 -0
- package/build/eslint.config.mjs +6 -0
- package/build/eslint.config.mjs.map +1 -0
- package/build/lib/commands/actions.d.ts +11 -0
- package/build/lib/commands/actions.d.ts.map +1 -0
- package/build/lib/commands/actions.js +212 -0
- package/build/lib/commands/actions.js.map +1 -0
- package/build/lib/commands/app.d.ts +12 -0
- package/build/lib/commands/app.d.ts.map +1 -0
- package/build/lib/commands/app.js +195 -0
- package/build/lib/commands/app.js.map +1 -0
- package/build/lib/commands/device.d.ts +3 -0
- package/build/lib/commands/device.d.ts.map +1 -0
- package/build/lib/commands/device.js +31 -0
- package/build/lib/commands/device.js.map +1 -0
- package/build/lib/commands/element.d.ts +15 -0
- package/build/lib/commands/element.d.ts.map +1 -0
- package/build/lib/commands/element.js +213 -0
- package/build/lib/commands/element.js.map +1 -0
- package/build/lib/commands/extension.d.ts +87 -0
- package/build/lib/commands/extension.d.ts.map +1 -0
- package/build/lib/commands/extension.js +511 -0
- package/build/lib/commands/extension.js.map +1 -0
- package/build/lib/commands/functions.d.ts +3 -0
- package/build/lib/commands/functions.d.ts.map +1 -0
- package/build/lib/commands/functions.js +194 -0
- package/build/lib/commands/functions.js.map +1 -0
- package/build/lib/commands/index.d.ts +126 -0
- package/build/lib/commands/index.d.ts.map +1 -0
- package/build/lib/commands/index.js +54 -0
- package/build/lib/commands/index.js.map +1 -0
- package/build/lib/commands/powershell.d.ts +6 -0
- package/build/lib/commands/powershell.d.ts.map +1 -0
- package/build/lib/commands/powershell.js +204 -0
- package/build/lib/commands/powershell.js.map +1 -0
- package/build/lib/commands/system.d.ts +4 -0
- package/build/lib/commands/system.d.ts.map +1 -0
- package/build/lib/commands/system.js +8 -0
- package/build/lib/commands/system.js.map +1 -0
- package/build/lib/constants.d.ts +2 -0
- package/build/lib/constants.d.ts.map +1 -0
- package/build/lib/constants.js +5 -0
- package/build/lib/constants.js.map +1 -0
- package/build/lib/constraints.d.ts +40 -0
- package/build/lib/constraints.d.ts.map +1 -0
- package/build/lib/constraints.js +42 -0
- package/build/lib/constraints.js.map +1 -0
- package/build/lib/driver.d.ts +33 -0
- package/build/lib/driver.d.ts.map +1 -0
- package/build/lib/driver.js +181 -0
- package/build/lib/driver.js.map +1 -0
- package/build/lib/enums.d.ts +89 -0
- package/build/lib/enums.d.ts.map +1 -0
- package/build/lib/enums.js +83 -0
- package/build/lib/enums.js.map +1 -0
- package/build/lib/powershell/common.d.ts +39 -0
- package/build/lib/powershell/common.d.ts.map +1 -0
- package/build/lib/powershell/common.js +121 -0
- package/build/lib/powershell/common.js.map +1 -0
- package/build/lib/powershell/conditions.d.ts +24 -0
- package/build/lib/powershell/conditions.d.ts.map +1 -0
- package/build/lib/powershell/conditions.js +131 -0
- package/build/lib/powershell/conditions.js.map +1 -0
- package/build/lib/powershell/converter.d.ts +3 -0
- package/build/lib/powershell/converter.d.ts.map +1 -0
- package/build/lib/powershell/converter.js +273 -0
- package/build/lib/powershell/converter.js.map +1 -0
- package/build/lib/powershell/core.d.ts +8 -0
- package/build/lib/powershell/core.d.ts.map +1 -0
- package/build/lib/powershell/core.js +30 -0
- package/build/lib/powershell/core.js.map +1 -0
- package/build/lib/powershell/elements.d.ts +68 -0
- package/build/lib/powershell/elements.d.ts.map +1 -0
- package/build/lib/powershell/elements.js +515 -0
- package/build/lib/powershell/elements.js.map +1 -0
- package/build/lib/powershell/index.d.ts +8 -0
- package/build/lib/powershell/index.d.ts.map +1 -0
- package/build/lib/powershell/index.js +24 -0
- package/build/lib/powershell/index.js.map +1 -0
- package/build/lib/powershell/regex.d.ts +19 -0
- package/build/lib/powershell/regex.d.ts.map +1 -0
- package/build/lib/powershell/regex.js +68 -0
- package/build/lib/powershell/regex.js.map +1 -0
- package/build/lib/powershell/types.d.ts +155 -0
- package/build/lib/powershell/types.d.ts.map +1 -0
- package/build/lib/powershell/types.js +141 -0
- package/build/lib/powershell/types.js.map +1 -0
- package/build/lib/util.d.ts +10 -0
- package/build/lib/util.d.ts.map +1 -0
- package/build/lib/util.js +51 -0
- package/build/lib/util.js.map +1 -0
- package/build/lib/winapi/types/index.d.ts +8 -0
- package/build/lib/winapi/types/index.d.ts.map +1 -0
- package/build/lib/winapi/types/index.js +24 -0
- package/build/lib/winapi/types/index.js.map +1 -0
- package/build/lib/winapi/types/input.d.ts +11 -0
- package/build/lib/winapi/types/input.d.ts.map +1 -0
- package/build/lib/winapi/types/input.js +12 -0
- package/build/lib/winapi/types/input.js.map +1 -0
- package/build/lib/winapi/types/keyeventf.d.ts +13 -0
- package/build/lib/winapi/types/keyeventf.d.ts.map +1 -0
- package/build/lib/winapi/types/keyeventf.js +14 -0
- package/build/lib/winapi/types/keyeventf.js.map +1 -0
- package/build/lib/winapi/types/mouseeventf.d.ts +34 -0
- package/build/lib/winapi/types/mouseeventf.d.ts.map +1 -0
- package/build/lib/winapi/types/mouseeventf.js +37 -0
- package/build/lib/winapi/types/mouseeventf.js.map +1 -0
- package/build/lib/winapi/types/scancode.d.ts +95 -0
- package/build/lib/winapi/types/scancode.d.ts.map +1 -0
- package/build/lib/winapi/types/scancode.js +96 -0
- package/build/lib/winapi/types/scancode.js.map +1 -0
- package/build/lib/winapi/types/systemmetric.d.ts +214 -0
- package/build/lib/winapi/types/systemmetric.d.ts.map +1 -0
- package/build/lib/winapi/types/systemmetric.js +215 -0
- package/build/lib/winapi/types/systemmetric.js.map +1 -0
- package/build/lib/winapi/types/virtualkey.d.ts +353 -0
- package/build/lib/winapi/types/virtualkey.d.ts.map +1 -0
- package/build/lib/winapi/types/virtualkey.js +354 -0
- package/build/lib/winapi/types/virtualkey.js.map +1 -0
- package/build/lib/winapi/types/xmousebutton.d.ts +7 -0
- package/build/lib/winapi/types/xmousebutton.d.ts.map +1 -0
- package/build/lib/winapi/types/xmousebutton.js +8 -0
- package/build/lib/winapi/types/xmousebutton.js.map +1 -0
- package/build/lib/winapi/user32.d.ts +56 -0
- package/build/lib/winapi/user32.d.ts.map +1 -0
- package/build/lib/winapi/user32.js +591 -0
- package/build/lib/winapi/user32.js.map +1 -0
- package/build/lib/xpath/core.d.ts +8 -0
- package/build/lib/xpath/core.d.ts.map +1 -0
- package/build/lib/xpath/core.js +593 -0
- package/build/lib/xpath/core.js.map +1 -0
- package/build/lib/xpath/functions.d.ts +4 -0
- package/build/lib/xpath/functions.d.ts.map +1 -0
- package/build/lib/xpath/functions.js +271 -0
- package/build/lib/xpath/functions.js.map +1 -0
- package/build/lib/xpath/index.d.ts +3 -0
- package/build/lib/xpath/index.d.ts.map +1 -0
- package/build/lib/xpath/index.js +19 -0
- package/build/lib/xpath/index.js.map +1 -0
- package/eslint.config.mjs +11 -0
- package/examples/C#/CalculatorTest/CalculatorTest/CalculatorSession.cs +44 -0
- package/examples/C#/CalculatorTest/CalculatorTest/CalculatorTest.csproj +24 -0
- package/examples/C#/CalculatorTest/CalculatorTest/ScenarioStandard.cs +121 -0
- package/examples/C#/CalculatorTest/CalculatorTest/ScenarioStandardInvoke.cs +121 -0
- package/examples/C#/CalculatorTest/CalculatorTest.sln +16 -0
- package/lib/commands/actions.ts +229 -0
- package/lib/commands/app.ts +227 -0
- package/lib/commands/device.ts +41 -0
- package/lib/commands/element.ts +242 -0
- package/lib/commands/extension.ts +636 -0
- package/lib/commands/functions.ts +192 -0
- package/lib/commands/index.ts +28 -0
- package/lib/commands/powershell.ts +243 -0
- package/lib/commands/system.ts +7 -0
- package/lib/constants.ts +1 -0
- package/lib/constraints.ts +43 -0
- package/lib/driver.ts +247 -0
- package/lib/enums.ts +96 -0
- package/lib/powershell/common.ts +137 -0
- package/lib/powershell/conditions.ts +169 -0
- package/lib/powershell/converter.ts +373 -0
- package/lib/powershell/core.ts +29 -0
- package/lib/powershell/elements.ts +584 -0
- package/lib/powershell/index.ts +7 -0
- package/lib/powershell/regex.ts +77 -0
- package/lib/powershell/types.ts +208 -0
- package/lib/util.ts +52 -0
- package/lib/winapi/types/index.ts +7 -0
- package/lib/winapi/types/input.ts +12 -0
- package/lib/winapi/types/keyeventf.ts +14 -0
- package/lib/winapi/types/mouseeventf.ts +37 -0
- package/lib/winapi/types/scancode.ts +96 -0
- package/lib/winapi/types/systemmetric.ts +215 -0
- package/lib/winapi/types/virtualkey.ts +354 -0
- package/lib/winapi/types/xmousebutton.ts +8 -0
- package/lib/winapi/user32.ts +842 -0
- package/lib/xpath/core.ts +699 -0
- package/lib/xpath/functions.ts +366 -0
- package/lib/xpath/index.ts +2 -0
- package/package.json +61 -0
- package/tsconfig.json +13 -0
- package/verify_driver.js +96 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { errors } from '@appium/base-driver';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
FunctionName,
|
|
5
|
+
ExprNode,
|
|
6
|
+
BOOLEAN,
|
|
7
|
+
CONCAT,
|
|
8
|
+
CONTAINS,
|
|
9
|
+
COUNT,
|
|
10
|
+
FALSE,
|
|
11
|
+
ROUND,
|
|
12
|
+
CEILING,
|
|
13
|
+
FLOOR,
|
|
14
|
+
ID,
|
|
15
|
+
LAST,
|
|
16
|
+
LOCAL_NAME,
|
|
17
|
+
NAME,
|
|
18
|
+
NORMALIZE_SPACE,
|
|
19
|
+
NOT,
|
|
20
|
+
POSITION,
|
|
21
|
+
STARTS_WITH,
|
|
22
|
+
STRING_LENGTH,
|
|
23
|
+
NUMBER,
|
|
24
|
+
STRING,
|
|
25
|
+
SUBSTRING_AFTER,
|
|
26
|
+
SUBSTRING_BEFORE,
|
|
27
|
+
SUBSTRING,
|
|
28
|
+
SUM,
|
|
29
|
+
TRANSLATE,
|
|
30
|
+
TRUE
|
|
31
|
+
} from 'xpath-analyzer';
|
|
32
|
+
|
|
33
|
+
import { AutomationElement, PropertyCondition, Property, PSInt32Array, TreeScope, FoundAutomationElement } from '../powershell';
|
|
34
|
+
import { $ } from '../util';
|
|
35
|
+
import { processExprNode } from './core';
|
|
36
|
+
|
|
37
|
+
const FUNCTION_ARGUMENT_ERROR = $`Function ${0}() requires ${1}.`;
|
|
38
|
+
|
|
39
|
+
export async function handleFunctionCall<T>(name: FunctionName, context: AutomationElement, sendPowerShellCommand: (command: string) => Promise<string>, ...args: ExprNode[]): Promise<T[]> {
|
|
40
|
+
const processArgs = async <T>(...args: ExprNode[]): Promise<T[][]> => {
|
|
41
|
+
const results: T[][] = [];
|
|
42
|
+
for (const arg of args) {
|
|
43
|
+
results.push(await processExprNode(arg, context, sendPowerShellCommand));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return results;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
switch (name) {
|
|
50
|
+
case NOT:
|
|
51
|
+
case BOOLEAN: {
|
|
52
|
+
if (args.length !== 1) {
|
|
53
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'exactly 1 argument'));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const [resultArray] = await processArgs(args[0]);
|
|
57
|
+
const result = Boolean(resultArray[0]);
|
|
58
|
+
return [name === NOT ? !result as T : result as T];
|
|
59
|
+
}
|
|
60
|
+
case CONCAT: {
|
|
61
|
+
if (args.length < 2) {
|
|
62
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'at least 2 arguments'));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resultArrays = await processArgs(...args);
|
|
66
|
+
if (resultArrays.some((resultArray) => resultArray.length > 1)) {
|
|
67
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'each argument to have either one or zero elements'));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const stringArray = convertProcessedExprNodesToStrings(...resultArrays.map((resultArray) => resultArray[0]));
|
|
71
|
+
return [String.prototype.concat(...stringArray) as T];
|
|
72
|
+
}
|
|
73
|
+
case STARTS_WITH:
|
|
74
|
+
case CONTAINS: {
|
|
75
|
+
if (args.length !== 2) {
|
|
76
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'exactly 2 arguments'));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const [firstArgResult, secondArgResult] = await processArgs<string | boolean | AutomationElement>(args[0], args[1]);
|
|
80
|
+
|
|
81
|
+
if (firstArgResult.length > 1 || secondArgResult.length > 1) {
|
|
82
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'each argument to have either one or zero elements'));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const [lhs] = firstArgResult;
|
|
86
|
+
const [rhs] = secondArgResult;
|
|
87
|
+
|
|
88
|
+
if (typeof lhs === 'boolean') {
|
|
89
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the first argument to be string, number or element'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (typeof rhs === 'boolean') {
|
|
93
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the second argument to be string, number or element'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const stringMethodMap = {
|
|
97
|
+
[STARTS_WITH]: 'startsWith',
|
|
98
|
+
[CONTAINS]: 'includes',
|
|
99
|
+
} as const satisfies Record<typeof name, keyof string>;
|
|
100
|
+
|
|
101
|
+
const [lhsString, rhsString] = convertProcessedExprNodesToStrings(firstArgResult, secondArgResult);
|
|
102
|
+
return [lhsString[stringMethodMap[name]](rhsString) as T];
|
|
103
|
+
}
|
|
104
|
+
case COUNT: {
|
|
105
|
+
if (args.length !== 1) {
|
|
106
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'exactly 1 argument'));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const resultArray = await processArgs(args[0]);
|
|
110
|
+
return [resultArray.length as T];
|
|
111
|
+
}
|
|
112
|
+
case TRUE:
|
|
113
|
+
case FALSE:
|
|
114
|
+
if (args.length > 0) {
|
|
115
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'to have 0 arguments'));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return [name === TRUE ? true as T : false as T];
|
|
119
|
+
case ROUND:
|
|
120
|
+
case CEILING:
|
|
121
|
+
case FLOOR: {
|
|
122
|
+
if (args.length !== 1) {
|
|
123
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'exactly 1 argument'));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const resultArray = await processArgs(args[0]);
|
|
127
|
+
const [num] = resultArray[0];
|
|
128
|
+
if (typeof num !== 'number') {
|
|
129
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the first argument to be a number'));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const mathMethodMap = {
|
|
133
|
+
[ROUND]: 'round',
|
|
134
|
+
[FLOOR]: 'floor',
|
|
135
|
+
[CEILING]: 'ceil',
|
|
136
|
+
} as const satisfies Record<typeof name, keyof Math>;
|
|
137
|
+
|
|
138
|
+
return [Math[mathMethodMap[name]](num) as T];
|
|
139
|
+
}
|
|
140
|
+
case ID: {
|
|
141
|
+
if (args.length !== 1) {
|
|
142
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'exactly 1 argument'));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const resultArray = await processArgs(args[0]);
|
|
146
|
+
const stringArray = convertProcessedExprNodesToStrings(...resultArray);
|
|
147
|
+
|
|
148
|
+
const ids = Array.from(new Set(stringArray.flatMap((string) => string.split(/\s+/))));
|
|
149
|
+
const conditions = ids.map((id) => new PropertyCondition(Property.RUNTIME_ID, new PSInt32Array(id.split('.').map(Number))));
|
|
150
|
+
const results: string[] = [];
|
|
151
|
+
for (const condition of conditions) {
|
|
152
|
+
results.push(await sendPowerShellCommand(AutomationElement.rootElement.findFirst(TreeScope.SUBTREE, condition).buildCommand()));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return results.map((result) => new FoundAutomationElement(result.trim()) as T);
|
|
156
|
+
}
|
|
157
|
+
case POSITION:
|
|
158
|
+
case LAST: {
|
|
159
|
+
if (args.length > 0) {
|
|
160
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'to have 0 arguments'));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const elIds = await sendPowerShellCommand(context.buildCommand());
|
|
164
|
+
const lastElementIndex = elIds.split('\n').map((id) => id.trim()).filter(Boolean).length;
|
|
165
|
+
return name === LAST ? [lastElementIndex as T] : Array.from({ length: lastElementIndex }, () => 0 as T);
|
|
166
|
+
}
|
|
167
|
+
case LOCAL_NAME:
|
|
168
|
+
case NAME: {
|
|
169
|
+
if (args.length > 1) {
|
|
170
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'no more than 1 argument'));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const [argResult] = await processArgs<AutomationElement>(args[0]);
|
|
174
|
+
|
|
175
|
+
if (argResult && argResult.length > 1) {
|
|
176
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'each argument to have either one or zero elements'));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const [element] = argResult ?? [context];
|
|
180
|
+
|
|
181
|
+
if (!(element instanceof AutomationElement)) {
|
|
182
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the first argument to be element'));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const result = await sendPowerShellCommand(element.buildGetTagNameCommand());
|
|
186
|
+
return result.split('\n').map((x) => x.trim() as T);
|
|
187
|
+
}
|
|
188
|
+
case NORMALIZE_SPACE: {
|
|
189
|
+
if (args.length > 1) {
|
|
190
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'no more than 1 argument'));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const [argResult] = await processArgs<AutomationElement | string>(args[0]);
|
|
194
|
+
|
|
195
|
+
if (argResult && argResult.length > 1) {
|
|
196
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'each argument to have either one or zero elements'));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!argResult[0] || argResult[0] instanceof AutomationElement) {
|
|
200
|
+
return ['' as T];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (typeof argResult[0] === 'string') {
|
|
204
|
+
return [argResult[0].trim().replace(/\s+/g, ' ') as T];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the first argument to be string or element'));
|
|
208
|
+
}
|
|
209
|
+
case STRING_LENGTH: {
|
|
210
|
+
if (args.length > 1) {
|
|
211
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'no more than 1 argument'));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const [argResult] = await processArgs<AutomationElement | string>(args[0]);
|
|
215
|
+
|
|
216
|
+
if (argResult && argResult.length > 1) {
|
|
217
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the first argument to have either one or zero elements'));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!argResult[0] || argResult[0] instanceof AutomationElement) {
|
|
221
|
+
return [0 as T];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (typeof argResult[0] === 'string') {
|
|
225
|
+
return [argResult[0].length as T];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the first argument to be string or element'));
|
|
229
|
+
}
|
|
230
|
+
case TRANSLATE:{
|
|
231
|
+
if (args.length !== 3) {
|
|
232
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'exactly 3 arguments'));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const [firstArgResult, secondArgResult, thirdArgResult] = await processArgs<string | AutomationElement>(args[0], args[1], args[2]);
|
|
236
|
+
|
|
237
|
+
if (firstArgResult.length > 1 || secondArgResult.length > 1 || thirdArgResult.length > 1) {
|
|
238
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'each argument to have either one or zero elements'));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const [[str], [from], [to]] = [firstArgResult, secondArgResult, thirdArgResult];
|
|
242
|
+
|
|
243
|
+
if (![str, from, to].every((x) => typeof x === 'string' || x instanceof AutomationElement)) {
|
|
244
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'each argument to be string or element'));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (str instanceof AutomationElement) {
|
|
248
|
+
return ['' as T];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (from instanceof AutomationElement) {
|
|
252
|
+
return [str as T];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const result = str.split('').map((char) => {
|
|
256
|
+
const index = from.indexOf(char);
|
|
257
|
+
return index !== -1 ? (to instanceof AutomationElement ? '' : to)[index] ?? '' : char;
|
|
258
|
+
}).join('');
|
|
259
|
+
|
|
260
|
+
return [result as T];
|
|
261
|
+
}
|
|
262
|
+
case NUMBER:
|
|
263
|
+
case STRING: {
|
|
264
|
+
if (args.length > 1) {
|
|
265
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'no more than 1 argument'));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const [firstArg] = await processArgs(args[0]);
|
|
269
|
+
|
|
270
|
+
if (firstArg && firstArg.length > 1) {
|
|
271
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the first argument to have either one or zero elements'));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return name === STRING ? [...convertProcessedExprNodesToStrings(firstArg ?? [context]) as T[]] : [...convertProcessedExprNodesToNumbers(firstArg ?? [context]) as T[]];
|
|
275
|
+
}
|
|
276
|
+
case SUBSTRING_AFTER:
|
|
277
|
+
case SUBSTRING_BEFORE: {
|
|
278
|
+
if (args.length !== 2) {
|
|
279
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'exactly 2 arguments'));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const [firstArgResult, secondArgResult] = await processArgs<string | boolean | AutomationElement>(args[0], args[1]);
|
|
283
|
+
|
|
284
|
+
if (firstArgResult.length > 1 || secondArgResult.length > 1) {
|
|
285
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'each argument to have either one or zero elements'));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const [lhs] = firstArgResult;
|
|
289
|
+
const [rhs] = secondArgResult;
|
|
290
|
+
|
|
291
|
+
if (typeof lhs === 'boolean') {
|
|
292
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the first argument to be string, number or element'));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (typeof rhs === 'boolean') {
|
|
296
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the second argument to be string, number or element'));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const [firstString] = convertProcessedExprNodesToStrings(firstArgResult);
|
|
300
|
+
const [secondString] = convertProcessedExprNodesToStrings(secondArgResult);
|
|
301
|
+
|
|
302
|
+
const index = firstString.indexOf(secondString);
|
|
303
|
+
|
|
304
|
+
if (index === -1) {
|
|
305
|
+
return ['' as T];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return name === SUBSTRING_BEFORE ? [firstString.slice(0, index) as T] : [firstString.slice(index + 1) as T];
|
|
309
|
+
}
|
|
310
|
+
case SUBSTRING: {
|
|
311
|
+
if (args.length < 2) {
|
|
312
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'at least 2 arguments'));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (args.length > 3) {
|
|
316
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'no more than 3 arguments'));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const [stringArg, fromArg, countArg] = await processArgs(...args);
|
|
320
|
+
|
|
321
|
+
if (typeof stringArg[0] === 'boolean' || typeof stringArg[0] === 'number') {
|
|
322
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the first argument to be string or element'));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const [string] = convertProcessedExprNodesToStrings(stringArg);
|
|
326
|
+
const [index] = fromArg;
|
|
327
|
+
const [count] = countArg;
|
|
328
|
+
|
|
329
|
+
if (typeof index !== 'number') {
|
|
330
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the second argument to be number'));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (typeof count !== 'number' && typeof count !== 'undefined') {
|
|
334
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'the second argument to be number'));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (count && count < 0) {
|
|
338
|
+
return ['' as T];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return [string.slice(index + 1, count ? index + count : string.length) as T];
|
|
342
|
+
}
|
|
343
|
+
case SUM: {
|
|
344
|
+
if (args.length !== 1) {
|
|
345
|
+
throw new errors.InvalidArgumentError(FUNCTION_ARGUMENT_ERROR.format(name, 'exactly 1 argument'));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const [arg] = await processArgs(args[0]);
|
|
349
|
+
const argAsNumbers = convertProcessedExprNodesToNumbers(arg);
|
|
350
|
+
return [argAsNumbers.reduce((a, b) => a + b) as T];
|
|
351
|
+
}
|
|
352
|
+
default:
|
|
353
|
+
throw new errors.InvalidSelectorError(`XPath function ${name}() not found.`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function convertProcessedExprNodesToStrings<T>(...arrayOfProcessedExprNodes: T[]): string[] {
|
|
358
|
+
return arrayOfProcessedExprNodes
|
|
359
|
+
.map((item) => (item instanceof AutomationElement) ? '' : item) // windows element xml representaton never contains text nodes
|
|
360
|
+
.map((item) => item === undefined || item === null ? '' : String(item)); // convert not found elements to empty string and others to string
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function convertProcessedExprNodesToNumbers<T>(...arrayOfProcessedExprNodes: T[]): number[] {
|
|
364
|
+
const arrayOfStrings = convertProcessedExprNodesToStrings(...arrayOfProcessedExprNodes);
|
|
365
|
+
return arrayOfStrings.map((str) => /^\s*(?<![\d.+-])[+-]?(?:\d*[.])?\d+(?![\d.])\s*$/.test(str) ? NaN : Number(str));
|
|
366
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "appium-novawindows2-driver",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Appium driver for Windows",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"appium",
|
|
7
|
+
"novawindows2",
|
|
8
|
+
"uiautomation",
|
|
9
|
+
"powershell",
|
|
10
|
+
"automated testing",
|
|
11
|
+
"windows"
|
|
12
|
+
],
|
|
13
|
+
"main": "build/lib/driver.js",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -b",
|
|
16
|
+
"watch": "tsc -b --watch",
|
|
17
|
+
"lint": "eslint .",
|
|
18
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
19
|
+
},
|
|
20
|
+
"author": "nguyenvanhuy0612",
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/nguyenvanhuy0612/appium-novawindows-driver.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/nguyenvanhuy0612/appium-novawindows-driver/issues"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"appium": "^3.1.0"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@appium/base-driver": "^10.1.0",
|
|
34
|
+
"bezier-easing": "^2.1.0",
|
|
35
|
+
"koffi": "^2.14.1",
|
|
36
|
+
"xpath-analyzer": "^3.0.1"
|
|
37
|
+
},
|
|
38
|
+
"appium": {
|
|
39
|
+
"driverName": "novawindows2",
|
|
40
|
+
"automationName": "NovaWindows",
|
|
41
|
+
"platformNames": [
|
|
42
|
+
"Windows"
|
|
43
|
+
],
|
|
44
|
+
"mainClass": "NovaWindowsDriver"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@appium/eslint-config-appium-ts": "^2.0.3",
|
|
48
|
+
"@appium/tsconfig": "^1.1.0",
|
|
49
|
+
"@appium/types": "^1.1.0",
|
|
50
|
+
"@eslint/js": "^9.38.0",
|
|
51
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
52
|
+
"@semantic-release/git": "^10.0.1",
|
|
53
|
+
"@types/node": "^24.8.1",
|
|
54
|
+
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
55
|
+
"eslint": "^9.38.0",
|
|
56
|
+
"semantic-release": "^25.0.1",
|
|
57
|
+
"typescript": "^5.9.3",
|
|
58
|
+
"typescript-eslint": "^8.46.1",
|
|
59
|
+
"webdriverio": "^9.21.0"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"extends": "@appium/tsconfig/tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"outDir": "build",
|
|
7
|
+
"strict": false,
|
|
8
|
+
"checkJs": true,
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
"noImplicitOverride": true,
|
|
11
|
+
},
|
|
12
|
+
"include": ["lib", "eslint.config.mjs"]
|
|
13
|
+
}
|
package/verify_driver.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const { remote } = require('webdriverio');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const opts = {
|
|
7
|
+
hostname: '127.0.0.1',
|
|
8
|
+
port: 4723,
|
|
9
|
+
path: '/',
|
|
10
|
+
capabilities: {
|
|
11
|
+
"appium:automationName": "NovaWindows",
|
|
12
|
+
"platformName": "Windows",
|
|
13
|
+
"appium:app": "Root", // Using Root to attach to desktop, verify this is supported
|
|
14
|
+
"appium:newCommandTimeout": 60
|
|
15
|
+
},
|
|
16
|
+
logLevel: 'error'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
console.log('Initializing session...');
|
|
20
|
+
let client;
|
|
21
|
+
try {
|
|
22
|
+
client = await remote(opts);
|
|
23
|
+
console.log('Session created successfully.');
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error('Failed to create session:', err);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// 1. Get Page Source
|
|
31
|
+
console.log('Test 1: Get Page Source (SKIPPED - too slow for Root)');
|
|
32
|
+
//const source = await client.getPageSource();
|
|
33
|
+
//console.log(`Page Source retrieval successful (Length: ${source.length} chars)`);
|
|
34
|
+
|
|
35
|
+
// 2. Maximize Window
|
|
36
|
+
// Note: For Root, maximize might not be applicable to the "desktop" window itself in the same way,
|
|
37
|
+
// but we can try getting window size or finding a window to maximize.
|
|
38
|
+
// For simplicity, we just log window size.
|
|
39
|
+
console.log('Test 2: Get Window Rect');
|
|
40
|
+
const rect = await client.getWindowRect();
|
|
41
|
+
console.log(`Window Rect: ${JSON.stringify(rect)}`);
|
|
42
|
+
|
|
43
|
+
// 3. Take Screenshot
|
|
44
|
+
console.log('Test 3: Take Screenshot');
|
|
45
|
+
const screenshotPath = path.resolve(__dirname, 'screenshot.png');
|
|
46
|
+
await client.saveScreenshot(screenshotPath);
|
|
47
|
+
if (fs.existsSync(screenshotPath)) {
|
|
48
|
+
console.log(`Screenshot saved to: ${screenshotPath}`);
|
|
49
|
+
} else {
|
|
50
|
+
console.error('Screenshot failed to save.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 4. Find Element
|
|
54
|
+
console.log('Test 4: Find Element (XPath)');
|
|
55
|
+
// Try to find the Taskbar or Start button.
|
|
56
|
+
// Common on Windows 10/11: "Starry" or name="Start"
|
|
57
|
+
// Let's try a generic find first
|
|
58
|
+
const element = await client.$('//*');
|
|
59
|
+
if (element.error) {
|
|
60
|
+
console.error('Failed to find root element');
|
|
61
|
+
} else {
|
|
62
|
+
console.log(`Found root element with ID: ${element.elementId}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 5. Get Window Handle
|
|
66
|
+
console.log('Test 5: Get Window Handle');
|
|
67
|
+
const handle = await client.getWindowHandle();
|
|
68
|
+
console.log(`Window Handle: ${handle}`);
|
|
69
|
+
|
|
70
|
+
// 6. Execute powershell command
|
|
71
|
+
console.log('Test 6: Execute powershell command');
|
|
72
|
+
const result = await client.executeScript('powerShell', [{ 'command': 'Get-Process' }]);
|
|
73
|
+
// console.log(`Result: ${JSON.stringify(result)}`); // Output can be huge, maybe truncate
|
|
74
|
+
console.log(`Result length: ${JSON.stringify(result).length} chars`);
|
|
75
|
+
|
|
76
|
+
// 7. Execute powershell command with exit
|
|
77
|
+
console.log('Test 7: Execute powershell command with exit');
|
|
78
|
+
try {
|
|
79
|
+
const resultExit = await client.executeScript('powerShell', [{ 'command': 'Get-Process; exit 0' }]);
|
|
80
|
+
console.log(`Result exit length: ${JSON.stringify(resultExit).length}`);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.log(`Test 7 encountered error as expected (or unexpected): ${e.message}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error('Test execution failed:', err);
|
|
87
|
+
} finally {
|
|
88
|
+
if (client) {
|
|
89
|
+
console.log('Deleting session...');
|
|
90
|
+
await client.deleteSession();
|
|
91
|
+
console.log('Session deleted.');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main();
|