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
package/lib/driver.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
+
import { BaseDriver, W3C_ELEMENT_KEY, errors } from '@appium/base-driver';
|
|
3
|
+
import { system } from 'appium/support';
|
|
4
|
+
import commands from './commands';
|
|
5
|
+
import {
|
|
6
|
+
UI_AUTOMATION_DRIVER_CONSTRAINTS,
|
|
7
|
+
NovaWindowsDriverConstraints
|
|
8
|
+
} from './constraints';
|
|
9
|
+
import {
|
|
10
|
+
assertSupportedEasingFunction
|
|
11
|
+
} from './util';
|
|
12
|
+
import {
|
|
13
|
+
Condition,
|
|
14
|
+
PropertyCondition,
|
|
15
|
+
AutomationElement,
|
|
16
|
+
FoundAutomationElement,
|
|
17
|
+
TreeScope,
|
|
18
|
+
Property,
|
|
19
|
+
convertStringToCondition,
|
|
20
|
+
PSString,
|
|
21
|
+
PSControlType,
|
|
22
|
+
PSInt32Array,
|
|
23
|
+
} from './powershell';
|
|
24
|
+
import { xpathToElIdOrIds } from './xpath';
|
|
25
|
+
import { setDpiAwareness } from './winapi/user32';
|
|
26
|
+
|
|
27
|
+
import type {
|
|
28
|
+
DefaultCreateSessionResult,
|
|
29
|
+
DriverData,
|
|
30
|
+
Element,
|
|
31
|
+
InitialOpts,
|
|
32
|
+
StringRecord,
|
|
33
|
+
W3CDriverCaps
|
|
34
|
+
} from '@appium/types';
|
|
35
|
+
|
|
36
|
+
type W3CNovaWindowsDriverCaps = W3CDriverCaps<NovaWindowsDriverConstraints>;
|
|
37
|
+
type DefaultWindowsCreateSessionResult = DefaultCreateSessionResult<NovaWindowsDriverConstraints>;
|
|
38
|
+
|
|
39
|
+
type KeyboardState = {
|
|
40
|
+
pressed: Set<string>,
|
|
41
|
+
shift: boolean,
|
|
42
|
+
ctrl: boolean,
|
|
43
|
+
meta: boolean,
|
|
44
|
+
alt: boolean,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const LOCATION_STRATEGIES = Object.freeze([
|
|
48
|
+
'id',
|
|
49
|
+
'name',
|
|
50
|
+
'xpath',
|
|
51
|
+
'tag name',
|
|
52
|
+
'class name',
|
|
53
|
+
'accessibility id',
|
|
54
|
+
'-windows uiautomation',
|
|
55
|
+
] as const);
|
|
56
|
+
|
|
57
|
+
export class NovaWindowsDriver extends BaseDriver<NovaWindowsDriverConstraints, StringRecord> {
|
|
58
|
+
isPowerShellSessionStarted: boolean = false;
|
|
59
|
+
powerShell?: ChildProcessWithoutNullStreams;
|
|
60
|
+
powerShellStdOut: string = '';
|
|
61
|
+
powerShellStdErr: string = '';
|
|
62
|
+
keyboardState: KeyboardState = {
|
|
63
|
+
pressed: new Set(),
|
|
64
|
+
alt: false,
|
|
65
|
+
ctrl: false,
|
|
66
|
+
meta: false,
|
|
67
|
+
shift: false,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
constructor(opts: InitialOpts = {} as InitialOpts, shouldValidateCaps = true) {
|
|
71
|
+
super(opts, shouldValidateCaps);
|
|
72
|
+
|
|
73
|
+
this.locatorStrategies = [...LOCATION_STRATEGIES];
|
|
74
|
+
this.desiredCapConstraints = UI_AUTOMATION_DRIVER_CONSTRAINTS;
|
|
75
|
+
|
|
76
|
+
for (const key in commands) { // TODO: create a decorator that will do that for the class
|
|
77
|
+
NovaWindowsDriver.prototype[key] = commands[key].bind(this);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override async findElement(strategy: string, selector: string): Promise<Element> {
|
|
82
|
+
[strategy, selector] = this.processSelector(strategy, selector);
|
|
83
|
+
return super.findElement(strategy, selector);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
override async findElements(strategy: string, selector: string): Promise<Element[]> {
|
|
87
|
+
[strategy, selector] = this.processSelector(strategy, selector);
|
|
88
|
+
return super.findElements(strategy, selector);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override async findElementFromElement(strategy: string, selector: string, elementId: string): Promise<Element> {
|
|
92
|
+
[strategy, selector] = this.processSelector(strategy, selector);
|
|
93
|
+
return super.findElementFromElement(strategy, selector, elementId);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override async findElementsFromElement(strategy: string, selector: string, elementId: string): Promise<Element[]> {
|
|
97
|
+
[strategy, selector] = this.processSelector(strategy, selector);
|
|
98
|
+
return super.findElementsFromElement(strategy, selector, elementId);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override async findElOrEls(strategy: typeof LOCATION_STRATEGIES[number], selector: string, mult: true, context?: string): Promise<Element[]>;
|
|
102
|
+
override async findElOrEls(strategy: typeof LOCATION_STRATEGIES[number], selector: string, mult: false, context?: string): Promise<Element>;
|
|
103
|
+
override async findElOrEls(strategy: typeof LOCATION_STRATEGIES[number], selector: string, mult: boolean, context?: string): Promise<Element | Element[]> {
|
|
104
|
+
let condition: Condition;
|
|
105
|
+
switch (strategy) {
|
|
106
|
+
case 'id':
|
|
107
|
+
condition = new PropertyCondition(Property.RUNTIME_ID, new PSInt32Array(selector.split('.').map(Number)));
|
|
108
|
+
break;
|
|
109
|
+
case 'tag name':
|
|
110
|
+
condition = new PropertyCondition(Property.CONTROL_TYPE, new PSControlType(selector));
|
|
111
|
+
break;
|
|
112
|
+
case 'accessibility id':
|
|
113
|
+
condition = new PropertyCondition(Property.AUTOMATION_ID, new PSString(selector));
|
|
114
|
+
break;
|
|
115
|
+
case 'name':
|
|
116
|
+
condition = new PropertyCondition(Property.NAME, new PSString(selector));
|
|
117
|
+
break;
|
|
118
|
+
case 'class name':
|
|
119
|
+
condition = new PropertyCondition(Property.CLASS_NAME, new PSString(selector));
|
|
120
|
+
break;
|
|
121
|
+
case '-windows uiautomation':
|
|
122
|
+
condition = convertStringToCondition(selector);
|
|
123
|
+
break;
|
|
124
|
+
case 'xpath':
|
|
125
|
+
return await xpathToElIdOrIds(selector, mult, context, this.sendPowerShellCommand.bind(this));
|
|
126
|
+
default:
|
|
127
|
+
throw new errors.InvalidArgumentError(`Invalid find strategy ${strategy}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const searchContext = context ? new FoundAutomationElement(context) : AutomationElement.automationRoot;
|
|
131
|
+
|
|
132
|
+
if (mult) {
|
|
133
|
+
const result = await this.sendPowerShellCommand(searchContext.findAll(TreeScope.DESCENDANTS, condition).buildCommand());
|
|
134
|
+
const elIds = result.split('\n').map((elId) => elId.trim()).filter(Boolean);
|
|
135
|
+
return elIds.filter(Boolean).map((elId) => ({ [W3C_ELEMENT_KEY]: elId }));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = await this.sendPowerShellCommand(searchContext.findFirst(TreeScope.DESCENDANTS, condition).buildCommand());
|
|
139
|
+
const elId = result.trim();
|
|
140
|
+
|
|
141
|
+
if (!elId) {
|
|
142
|
+
throw new errors.NoSuchElementError();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { [W3C_ELEMENT_KEY]: elId };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
override async createSession(
|
|
149
|
+
jwpCaps: W3CNovaWindowsDriverCaps,
|
|
150
|
+
reqCaps?: W3CNovaWindowsDriverCaps,
|
|
151
|
+
w3cCaps?: W3CNovaWindowsDriverCaps,
|
|
152
|
+
driverData?: DriverData[]
|
|
153
|
+
): Promise<DefaultWindowsCreateSessionResult> {
|
|
154
|
+
if (!system.isWindows()) {
|
|
155
|
+
this.log.errorWithException('Windows UI Automation tests only run on Windows.');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (typeof w3cCaps?.alwaysMatch?.['appium:appTopLevelWindow'] === 'number') {
|
|
159
|
+
w3cCaps.alwaysMatch['appium:appTopLevelWindow'] = String(w3cCaps.alwaysMatch['appium:appTopLevelWindow']);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (typeof w3cCaps?.firstMatch?.some['appium:appTopLevelWindow'] === 'number') {
|
|
163
|
+
w3cCaps.firstMatch['appium:appTopLevelWindow'] = w3cCaps.firstMatch['appium:appTopLevelWindow'].map(String);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
this.log.debug('Creating NovaWindows driver session...');
|
|
168
|
+
const [sessionId, caps] = await super.createSession(jwpCaps, reqCaps, w3cCaps, driverData);
|
|
169
|
+
if (caps.smoothPointerMove) {
|
|
170
|
+
assertSupportedEasingFunction(caps.smoothPointerMove);
|
|
171
|
+
}
|
|
172
|
+
if (caps.app && caps.appTopLevelWindow) {
|
|
173
|
+
throw new errors.InvalidArgumentError('Invalid capabilities. Specify either app or appTopLevelWindow.');
|
|
174
|
+
}
|
|
175
|
+
if (this.caps.shouldCloseApp === undefined) {
|
|
176
|
+
this.caps.shouldCloseApp = true; // set default value
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await this.startPowerShellSession();
|
|
180
|
+
|
|
181
|
+
if (this.caps.prerun) {
|
|
182
|
+
this.log.info('Executing prerun PowerShell script...');
|
|
183
|
+
await this.executePowerShellScript(this.caps.prerun as Exclude<Parameters<typeof commands['executePowerShellScript']>[0], string>);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
setDpiAwareness();
|
|
187
|
+
this.log.debug(`Started session ${sessionId}.`);
|
|
188
|
+
return [sessionId, caps];
|
|
189
|
+
} catch (e) {
|
|
190
|
+
await this.deleteSession();
|
|
191
|
+
throw e;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
override async deleteSession(sessionId?: string | null | undefined): Promise<void> {
|
|
196
|
+
this.log.debug('Deleting NovaWindows driver session...');
|
|
197
|
+
|
|
198
|
+
if (this.caps.shouldCloseApp && this.caps.app && this.caps.app.toLowerCase() !== 'root') {
|
|
199
|
+
try {
|
|
200
|
+
const result = await this.sendPowerShellCommand(AutomationElement.automationRoot.buildCommand());
|
|
201
|
+
const elementId = result.split('\n').map((id) => id.trim()).filter(Boolean)[0];
|
|
202
|
+
if (elementId) {
|
|
203
|
+
await this.sendPowerShellCommand(new FoundAutomationElement(elementId).buildCloseCommand());
|
|
204
|
+
}
|
|
205
|
+
} catch {
|
|
206
|
+
// noop
|
|
207
|
+
}
|
|
208
|
+
} // change to close the whole process, not only the window
|
|
209
|
+
await this.terminatePowerShellSession();
|
|
210
|
+
|
|
211
|
+
if (this.caps.postrun) {
|
|
212
|
+
this.log.info('Executing postrun PowerShell script...');
|
|
213
|
+
await this.executePowerShellScript(this.caps.postrun as Exclude<Parameters<typeof commands['executePowerShellScript']>[0], string>);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await super.deleteSession(sessionId);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private processSelector(strategy: string, selector: string): [string, string] {
|
|
220
|
+
if (strategy !== 'css selector') {
|
|
221
|
+
return [strategy, selector];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.log.warn('Warning: Use Appium mobile selectors instead of Selenium By, since most of them are based on CSS.');
|
|
225
|
+
const digitRegex = /\\3(\d) /;
|
|
226
|
+
|
|
227
|
+
if (selector.startsWith('.')) {
|
|
228
|
+
selector = selector.substring(1).replace(digitRegex, '$1');
|
|
229
|
+
strategy = 'class name';
|
|
230
|
+
return [strategy, selector];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (selector.startsWith('#')) {
|
|
234
|
+
selector = selector.substring(1).replace(digitRegex, '$1');
|
|
235
|
+
strategy = 'id';
|
|
236
|
+
return [strategy, selector];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (selector.startsWith('*[name')) {
|
|
240
|
+
selector = selector.substring(selector.indexOf('"') + 1, selector.lastIndexOf('"')).replace(digitRegex, '$1');
|
|
241
|
+
strategy = 'name';
|
|
242
|
+
return [strategy, selector];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return [strategy, selector];
|
|
246
|
+
}
|
|
247
|
+
}
|
package/lib/enums.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
|
|
2
|
+
? Acc[number]
|
|
3
|
+
: Enumerate<N, [...Acc, Acc['length']]>
|
|
4
|
+
|
|
5
|
+
type IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>> | T
|
|
6
|
+
|
|
7
|
+
type AllFlagsValue<N extends number, A extends any[] = []> = [N] extends [Partial<A>['length']] ? A['length'] : UnionFlags<N, [0, ...A, ...A]>;
|
|
8
|
+
type UnionFlags<N extends number, A extends any[] = []> = IntRange<0, AllFlagsValue<N, A>>;
|
|
9
|
+
|
|
10
|
+
export type Enum<T> = T[keyof T];
|
|
11
|
+
export type FlagsEnum<T> = Enum<T> extends number ? UnionFlags<Enum<T>> : never;
|
|
12
|
+
|
|
13
|
+
export const Key = Object.freeze({
|
|
14
|
+
NULL: '\uE000',
|
|
15
|
+
CANCEL: '\uE001',
|
|
16
|
+
HELP: '\uE002',
|
|
17
|
+
BACKSPACE: '\uE003',
|
|
18
|
+
TAB: '\uE004',
|
|
19
|
+
CLEAR: '\uE005',
|
|
20
|
+
RETURN: '\uE006',
|
|
21
|
+
ENTER: '\uE007',
|
|
22
|
+
SHIFT: '\uE008',
|
|
23
|
+
CONTROL: '\uE009',
|
|
24
|
+
ALT: '\uE00A',
|
|
25
|
+
PAUSE: '\uE00B',
|
|
26
|
+
ESCAPE: '\uE00C',
|
|
27
|
+
SPACE: '\uE00D',
|
|
28
|
+
PAGE_UP: '\uE00E',
|
|
29
|
+
PAGE_DOWN: '\uE00F',
|
|
30
|
+
END: '\uE010',
|
|
31
|
+
HOME: '\uE011',
|
|
32
|
+
LEFT: '\uE012',
|
|
33
|
+
UP: '\uE013',
|
|
34
|
+
RIGHT: '\uE014',
|
|
35
|
+
DOWN: '\uE015',
|
|
36
|
+
INSERT: '\uE016',
|
|
37
|
+
DELETE: '\uE017',
|
|
38
|
+
SEMICOLON: '\uE018',
|
|
39
|
+
EQUALS: '\uE019',
|
|
40
|
+
NUMPAD0: '\uE01A',
|
|
41
|
+
NUMPAD1: '\uE01B',
|
|
42
|
+
NUMPAD2: '\uE01C',
|
|
43
|
+
NUMPAD3: '\uE01D',
|
|
44
|
+
NUMPAD4: '\uE01E',
|
|
45
|
+
NUMPAD5: '\uE01F',
|
|
46
|
+
NUMPAD6: '\uE020',
|
|
47
|
+
NUMPAD7: '\uE021',
|
|
48
|
+
NUMPAD8: '\uE022',
|
|
49
|
+
NUMPAD9: '\uE023',
|
|
50
|
+
MULTIPLY: '\uE024',
|
|
51
|
+
ADD: '\uE025',
|
|
52
|
+
SEPARATOR: '\uE026',
|
|
53
|
+
SUBTRACT: '\uE027',
|
|
54
|
+
DECIMAL: '\uE028',
|
|
55
|
+
DIVIDE: '\uE029',
|
|
56
|
+
F1: '\uE031',
|
|
57
|
+
F2: '\uE032',
|
|
58
|
+
F3: '\uE033',
|
|
59
|
+
F4: '\uE034',
|
|
60
|
+
F5: '\uE035',
|
|
61
|
+
F6: '\uE036',
|
|
62
|
+
F7: '\uE037',
|
|
63
|
+
F8: '\uE038',
|
|
64
|
+
F9: '\uE039',
|
|
65
|
+
F10: '\uE03A',
|
|
66
|
+
F11: '\uE03B',
|
|
67
|
+
F12: '\uE03C',
|
|
68
|
+
META: '\uE03D',
|
|
69
|
+
ZENKAKUHANKAKU: '\uE040',
|
|
70
|
+
R_SHIFT: '\uE050',
|
|
71
|
+
R_CONTROL: '\uE051',
|
|
72
|
+
R_ALT: '\uE052',
|
|
73
|
+
R_META: '\uE053',
|
|
74
|
+
R_PAGEUP: '\uE054',
|
|
75
|
+
R_PAGEDOWN: '\uE055',
|
|
76
|
+
R_END: '\uE056',
|
|
77
|
+
R_HOME: '\uE057',
|
|
78
|
+
R_ARROWLEFT: '\uE058',
|
|
79
|
+
R_ARROWUP: '\uE059',
|
|
80
|
+
R_ARROWRIGHT: '\uE05A',
|
|
81
|
+
R_ARROWDOWN: '\uE05B',
|
|
82
|
+
R_INSERT: '\uE05C',
|
|
83
|
+
R_DELETE: '\uE05D',
|
|
84
|
+
} as const);
|
|
85
|
+
|
|
86
|
+
export type Key = Enum<typeof Key>;
|
|
87
|
+
|
|
88
|
+
export const ClickType = Object.freeze({
|
|
89
|
+
LEFT: 'left',
|
|
90
|
+
MIDDLE: 'middle',
|
|
91
|
+
RIGHT: 'right',
|
|
92
|
+
BACK: 'back',
|
|
93
|
+
FORWARD: 'forward'
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export type ClickType = Enum<typeof ClickType>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { W3C_ELEMENT_KEY, errors } from '@appium/base-driver';
|
|
2
|
+
import { Element, Position, Rect } from '@appium/types';
|
|
3
|
+
import {
|
|
4
|
+
AutomationHeadingLevel,
|
|
5
|
+
ControlType,
|
|
6
|
+
ExtraControlType,
|
|
7
|
+
OrientationType,
|
|
8
|
+
} from './types';
|
|
9
|
+
import { FoundAutomationElement } from './elements';
|
|
10
|
+
import { PSObject } from './core';
|
|
11
|
+
|
|
12
|
+
export class PSString extends PSObject {
|
|
13
|
+
constructor(value: string) {
|
|
14
|
+
const escapedUnicodeString = value.split('')
|
|
15
|
+
.map((c) => /* ps1 */ `$([char]0x${c.charCodeAt(0).toString(16).padStart(4, '0')})`)
|
|
16
|
+
.join('');
|
|
17
|
+
super(`"${escapedUnicodeString}"`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class PSBoolean extends PSObject {
|
|
22
|
+
constructor(value: boolean) {
|
|
23
|
+
if (typeof value !== 'boolean') {
|
|
24
|
+
throw new errors.InvalidArgumentError(`PSBoolean accepts only boolean in the constructor, but got '${value}'.`);
|
|
25
|
+
}
|
|
26
|
+
super(value ? /* ps1 */ `$true` : /* ps1 */ `$false`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class PSInt32 extends PSObject {
|
|
31
|
+
constructor(value: number) {
|
|
32
|
+
if (!Number.isInteger(value)) {
|
|
33
|
+
throw new errors.InvalidArgumentError(`PSInt32 accepts only integer values in the constructor, but got '${value}'.`);
|
|
34
|
+
}
|
|
35
|
+
super(value.toString());
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class PSInt32Array extends PSObject {
|
|
40
|
+
constructor(value: number[]) {
|
|
41
|
+
if (!(Array.isArray(value) && value.every(Number.isInteger))) {
|
|
42
|
+
throw new errors.InvalidArgumentError(`PSInt32Array accepts only array of integers in the constructor, but got ${Array.isArray(value) ? `[${value}]` : `'${value}'`}.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
super(/* ps1 */ `[int32[]] @(${value.join(', ')})`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class PSAutomationHeadingLevel extends PSObject {
|
|
50
|
+
readonly originalValue: string;
|
|
51
|
+
|
|
52
|
+
constructor(value: string) {
|
|
53
|
+
if (!Object.values(AutomationHeadingLevel).includes(value.toLowerCase() as AutomationHeadingLevel)) {
|
|
54
|
+
throw new errors.InvalidArgumentError(`PSAutomationHeadingLevel accepts valid AutomationHeadingLevel enum value in the constructor, but got '${value}'.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
super(/* ps1 */ `[AutomationHeadingLevel]::${value}`);
|
|
58
|
+
this.originalValue = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class PSOrientationType extends PSObject {
|
|
63
|
+
readonly originalValue: string;
|
|
64
|
+
|
|
65
|
+
constructor(value: string) {
|
|
66
|
+
if (!Object.values(OrientationType).includes(value.toLowerCase() as OrientationType)) {
|
|
67
|
+
throw new errors.InvalidArgumentError(`PSOrientationType accepts valid OrientationType enum value in the constructor, but got '${value}'.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
super(/* ps1 */ `[OrientationType]::${value}`);
|
|
71
|
+
this.originalValue = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class PSControlType extends PSObject {
|
|
76
|
+
constructor(value: string) {
|
|
77
|
+
if (![...Object.values(ControlType), ...Object.values(ExtraControlType)].includes(value.toLowerCase() as ControlType)) {
|
|
78
|
+
throw new errors.InvalidArgumentError(`PSControlType accepts a valid ControlType in the constructor, but got '${value}'.`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (ExtraControlType.SEMANTIC_ZOOM === value.toLowerCase()) {
|
|
82
|
+
super('semantic zoom');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (ExtraControlType.APP_BAR === value.toLowerCase()) {
|
|
87
|
+
super('app bar');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
super(/* ps1 */ `[ControlType]::${value}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class PSPoint extends PSObject {
|
|
96
|
+
constructor(value: Position) {
|
|
97
|
+
const requiredFields = ['x', 'y'];
|
|
98
|
+
if (!(Object.keys(value).every(requiredFields.includes) && typeof value.x === 'number' && typeof value.y === 'number')) {
|
|
99
|
+
throw new errors.InvalidArgumentError('PSPoint accepts a Position object { x: number, y: number } in the constructor.');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
super(/* ps1 */ `[System.Windows.Point]::new(${value.x}, ${value.y})`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export class PSRect extends PSObject {
|
|
107
|
+
constructor(value: Rect) {
|
|
108
|
+
const requiredFields = ['x', 'y', 'width', 'height'];
|
|
109
|
+
if (!(Object.keys(value).every(requiredFields.includes) && typeof value.x === 'number' && typeof value.y === 'number' && typeof value.width === 'number' && typeof value.height === 'number')) {
|
|
110
|
+
throw new errors.InvalidArgumentError('PSRect accepts a Rect object { x: number, y: number, width: number, height: number } in the constructor.');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
super(/* ps1 */ `[System.Windows.Rect]::new(${value.x}, ${value.y}, ${value.width}, ${value.height})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class PSAutomationElement extends PSObject {
|
|
118
|
+
constructor(value: Element) {
|
|
119
|
+
if (!value[W3C_ELEMENT_KEY]) {
|
|
120
|
+
throw new errors.InvalidArgumentError('PSAutomationElement accepts a valid Appium Element in the constructor.');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
super(new FoundAutomationElement(value[W3C_ELEMENT_KEY]).toString());
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class PSCultureInfo extends PSObject {
|
|
128
|
+
constructor(name: string, useUserOverride?: boolean)
|
|
129
|
+
constructor(culture: number, useUserOverride?: boolean)
|
|
130
|
+
constructor(nameOrCulture: string | number, useUserOverride?: boolean) {
|
|
131
|
+
if (typeof nameOrCulture !== 'string' || (typeof nameOrCulture === 'number' && nameOrCulture < 0)) {
|
|
132
|
+
throw new errors.InvalidArgumentError('PSCultureInfo accepts a string or positive integer value in the constructor.');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
super(/* ps1 */ `[System.Globalization.CultureInfo]::new(${typeof nameOrCulture === 'string' ? `'${nameOrCulture}'` : nameOrCulture}${useUserOverride !== undefined ? `$${useUserOverride}` : ''})`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { errors } from '@appium/base-driver';
|
|
2
|
+
import { $ } from '../util';
|
|
3
|
+
import { PSObject } from './core';
|
|
4
|
+
import {
|
|
5
|
+
Property,
|
|
6
|
+
CultureInfoProperty,
|
|
7
|
+
AutomationHeadingLevelProperty,
|
|
8
|
+
PointProperty,
|
|
9
|
+
ControlTypeProperty,
|
|
10
|
+
AutomationElementProperty,
|
|
11
|
+
OrientationTypeProperty,
|
|
12
|
+
RectProperty,
|
|
13
|
+
Int32Property,
|
|
14
|
+
Int32ArrayProperty,
|
|
15
|
+
StringProperty,
|
|
16
|
+
BooleanProperty,
|
|
17
|
+
OrientationType,
|
|
18
|
+
AutomationHeadingLevel,
|
|
19
|
+
} from './types';
|
|
20
|
+
import {
|
|
21
|
+
PSAutomationElement,
|
|
22
|
+
PSAutomationHeadingLevel,
|
|
23
|
+
PSBoolean,
|
|
24
|
+
PSControlType,
|
|
25
|
+
PSCultureInfo,
|
|
26
|
+
PSInt32,
|
|
27
|
+
PSInt32Array,
|
|
28
|
+
PSOrientationType,
|
|
29
|
+
PSPoint,
|
|
30
|
+
PSRect,
|
|
31
|
+
PSString,
|
|
32
|
+
} from './common';
|
|
33
|
+
|
|
34
|
+
const PROPERTY_CONDITION = $ /* ps1 */ `[PropertyCondition]::new([AutomationElement]::${0}Property, ${1})`;
|
|
35
|
+
const AND_CONDITION = $ /* ps1 */ `[AndCondition]::new(${0})`;
|
|
36
|
+
const OR_CONDITION = $ /* ps1 */ `[OrCondition]::new(${0})`;
|
|
37
|
+
const NOT_CONDITION = $ /* ps1 */ `[NotCondition]::new(${0})`;
|
|
38
|
+
const TRUE_CONDITION = /* ps1 */ `[Condition]::TrueCondition`;
|
|
39
|
+
const FALSE_CONDITION = /* ps1 */ `[Condition]::FalseCondition`;
|
|
40
|
+
|
|
41
|
+
export abstract class Condition extends PSObject {
|
|
42
|
+
constructor(command: string) {
|
|
43
|
+
super(command);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class PropertyCondition extends Condition {
|
|
48
|
+
constructor(property: Property, value: PSObject) {
|
|
49
|
+
property = property.toLowerCase().endsWith('property') ? property.slice(0, property.length - 8) as Property : property;
|
|
50
|
+
|
|
51
|
+
if (Object.values(BooleanProperty).includes(property as BooleanProperty)) {
|
|
52
|
+
assertPSObjectType(value, PSBoolean);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Object.values(Int32Property).includes(property as Int32Property)) {
|
|
56
|
+
assertPSObjectType(value, PSInt32);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (Object.values(StringProperty).includes(property as StringProperty)) {
|
|
60
|
+
assertPSObjectType(value, PSString);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (Object.values(Int32ArrayProperty).includes(property as Int32ArrayProperty)) {
|
|
64
|
+
assertPSObjectType(value, PSInt32Array);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (Object.values(PointProperty).includes(property as PointProperty)) {
|
|
68
|
+
assertPSObjectType(value, PSPoint);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (Object.values(RectProperty).includes(property as RectProperty)) {
|
|
72
|
+
assertPSObjectType(value, PSRect);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (Object.values(ControlTypeProperty).includes(property as ControlTypeProperty)) {
|
|
76
|
+
assertPSObjectType(value, PSControlType);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (Object.values(AutomationElementProperty).includes(property as AutomationElementProperty)) {
|
|
80
|
+
assertPSObjectType(value, PSAutomationElement);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (Object.values(OrientationTypeProperty).includes(property as OrientationTypeProperty)) {
|
|
84
|
+
try {
|
|
85
|
+
assertPSObjectType(value, PSOrientationType);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
if (value instanceof PSAutomationHeadingLevel && value.originalValue.toLowerCase() === AutomationHeadingLevel.NONE) {
|
|
88
|
+
value = new PSOrientationType(OrientationType.NONE);
|
|
89
|
+
} else if (!(value instanceof PSInt32) || Number(value.toString()) < 0 || Number(value.toString()) >= Object.keys(AutomationHeadingLevel).length) {
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (Object.values(AutomationHeadingLevelProperty).includes(property as AutomationHeadingLevelProperty)) {
|
|
96
|
+
try {
|
|
97
|
+
assertPSObjectType(value, PSAutomationHeadingLevel);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
if (value instanceof PSOrientationType && value.originalValue.toLowerCase() === OrientationType.NONE) {
|
|
100
|
+
value = new PSAutomationHeadingLevel(AutomationHeadingLevel.NONE);
|
|
101
|
+
} else if (!(value instanceof PSInt32) || Number(value.toString()) < 0 || Number(value.toString()) >= Object.keys(OrientationType).length) {
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (Object.values(CultureInfoProperty).includes(property as CultureInfoProperty)) {
|
|
108
|
+
assertPSObjectType(value, PSCultureInfo);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
super(PROPERTY_CONDITION.format(property, value));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class AndCondition extends Condition {
|
|
116
|
+
constructor(...conditions: Condition[]) {
|
|
117
|
+
if (!conditions.every((arg) => arg instanceof Condition)) {
|
|
118
|
+
throw new errors.InvalidArgumentError(`AndCondition expects Conditions as args but received ${(conditions.find((x) => !(x instanceof Condition)) as unknown)?.constructor.name}.`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (conditions.length < 2) {
|
|
122
|
+
throw new errors.InvalidArgumentError(`AndCondition must have at least 2 conditions, but received ${conditions.length}.`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
super(AND_CONDITION.format(conditions.join(', ')));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class OrCondition extends Condition {
|
|
130
|
+
constructor(...conditions: Condition[]) {
|
|
131
|
+
if (!conditions.every((arg) => arg instanceof Condition)) {
|
|
132
|
+
throw new errors.InvalidArgumentError(`OrCondition expects Conditions as args but received ${(conditions.find((x) => !(x instanceof Condition)) as unknown)?.constructor.name}.`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (conditions.length < 2) {
|
|
136
|
+
throw new errors.InvalidArgumentError(`OrCondition must have at least 2 conditions, but received ${conditions.length}.`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
super(OR_CONDITION.format(conditions.join(', ')));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export class NotCondition extends Condition {
|
|
144
|
+
constructor(condition: Condition) {
|
|
145
|
+
if (!(condition instanceof Condition)) {
|
|
146
|
+
throw new errors.InvalidArgumentError(`AndCondition expects Conditions as args but received ${(condition as unknown)?.constructor.name}.`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
super(NOT_CONDITION.format(condition));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class TrueCondition extends Condition {
|
|
154
|
+
constructor() {
|
|
155
|
+
super(TRUE_CONDITION);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export class FalseCondition extends Condition {
|
|
160
|
+
constructor() {
|
|
161
|
+
super(FALSE_CONDITION);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function assertPSObjectType(obj: PSObject, type: new (...args: any[]) => PSObject) {
|
|
166
|
+
if (!(obj instanceof type)) {
|
|
167
|
+
throw new errors.InvalidArgumentError(`Property expected type ${type.name} but got ${(obj as object)?.constructor.name}.`);
|
|
168
|
+
}
|
|
169
|
+
}
|