appium-novawindows2-driver 0.1.3 → 0.1.5
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/LICENSE +201 -201
- package/README.md +557 -557
- package/build/lib/commands/actions.d.ts.map +1 -1
- package/build/lib/commands/actions.js +3 -4
- package/build/lib/commands/actions.js.map +1 -1
- package/build/lib/commands/app.d.ts.map +1 -1
- package/build/lib/commands/app.js +53 -45
- package/build/lib/commands/app.js.map +1 -1
- package/build/lib/commands/device.d.ts.map +1 -1
- package/build/lib/commands/device.js +2 -0
- package/build/lib/commands/device.js.map +1 -1
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +42 -12
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/extension.d.ts.map +1 -1
- package/build/lib/commands/extension.js +29 -17
- package/build/lib/commands/extension.js.map +1 -1
- package/build/lib/commands/file.d.ts +5 -0
- package/build/lib/commands/file.d.ts.map +1 -0
- package/build/lib/commands/file.js +49 -0
- package/build/lib/commands/file.js.map +1 -0
- package/build/lib/commands/functions.d.ts.map +1 -1
- package/build/lib/commands/functions.js +189 -187
- package/build/lib/commands/functions.js.map +1 -1
- package/build/lib/commands/index.d.ts +3 -0
- package/build/lib/commands/index.d.ts.map +1 -1
- package/build/lib/commands/index.js +2 -0
- package/build/lib/commands/index.js.map +1 -1
- package/build/lib/commands/powershell.d.ts.map +1 -1
- package/build/lib/commands/powershell.js +114 -68
- package/build/lib/commands/powershell.js.map +1 -1
- package/build/lib/constraints.d.ts +18 -0
- package/build/lib/constraints.d.ts.map +1 -1
- package/build/lib/constraints.js +18 -0
- package/build/lib/constraints.js.map +1 -1
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +35 -4
- package/build/lib/driver.js.map +1 -1
- package/build/lib/powershell/converter.d.ts.map +1 -1
- package/build/lib/powershell/converter.js +12 -0
- package/build/lib/powershell/converter.js.map +1 -1
- package/build/lib/powershell/elements.d.ts.map +1 -1
- package/build/lib/powershell/elements.js +270 -265
- package/build/lib/powershell/elements.js.map +1 -1
- package/build/lib/winapi/user32.js.map +1 -1
- package/build/lib/xpath/core.d.ts +2 -2
- package/build/lib/xpath/core.d.ts.map +1 -1
- package/build/lib/xpath/core.js +32 -26
- package/build/lib/xpath/core.js.map +1 -1
- package/build/lib/xpath/functions.d.ts +1 -1
- package/build/lib/xpath/functions.d.ts.map +1 -1
- package/build/lib/xpath/functions.js +2 -2
- package/build/lib/xpath/functions.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +67 -62
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -97
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -33
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -28
- package/.github/workflows/lint-build.yml +0 -30
- package/.github/workflows/release.yml +0 -39
- package/.releaserc +0 -41
- package/CHANGELOG.md +0 -33
- package/eslint.config.mjs +0 -11
- package/examples/api_test.js +0 -69
- package/examples/concurrency_test.js +0 -82
- package/examples/debug_test.js +0 -36
- package/examples/stress_test.js +0 -94
- package/examples/verify_driver.js +0 -142
- package/lib/commands/actions.ts +0 -229
- package/lib/commands/app.ts +0 -227
- package/lib/commands/device.ts +0 -41
- package/lib/commands/element.ts +0 -242
- package/lib/commands/extension.ts +0 -640
- package/lib/commands/functions.ts +0 -192
- package/lib/commands/index.ts +0 -28
- package/lib/commands/powershell.ts +0 -256
- package/lib/commands/system.ts +0 -7
- package/lib/constants.ts +0 -1
- package/lib/constraints.ts +0 -43
- package/lib/driver.ts +0 -266
- package/lib/enums.ts +0 -96
- package/lib/powershell/common.ts +0 -137
- package/lib/powershell/conditions.ts +0 -169
- package/lib/powershell/converter.ts +0 -373
- package/lib/powershell/core.ts +0 -29
- package/lib/powershell/elements.ts +0 -584
- package/lib/powershell/index.ts +0 -7
- package/lib/powershell/regex.ts +0 -77
- package/lib/powershell/types.ts +0 -208
- package/lib/util.ts +0 -52
- package/lib/winapi/types/index.ts +0 -7
- package/lib/winapi/types/input.ts +0 -12
- package/lib/winapi/types/keyeventf.ts +0 -14
- package/lib/winapi/types/mouseeventf.ts +0 -37
- package/lib/winapi/types/scancode.ts +0 -96
- package/lib/winapi/types/systemmetric.ts +0 -215
- package/lib/winapi/types/virtualkey.ts +0 -354
- package/lib/winapi/types/xmousebutton.ts +0 -8
- package/lib/winapi/user32.ts +0 -842
- package/lib/xpath/core.ts +0 -699
- package/lib/xpath/functions.ts +0 -366
- package/lib/xpath/index.ts +0 -2
- package/tsconfig.json +0 -13
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { pwsh } from '../powershell';
|
|
2
|
-
|
|
3
|
-
export const FIND_CHILDREN_RECURSIVELY = pwsh /* ps1 */ `
|
|
4
|
-
function Find-ChildrenRecursively {
|
|
5
|
-
param (
|
|
6
|
-
[Parameter(Mandatory=$true)]
|
|
7
|
-
[AutomationElement]$element,
|
|
8
|
-
[Parameter(Mandatory=$true)]
|
|
9
|
-
[Condition]$condition,
|
|
10
|
-
[Parameter(Mandatory=$false)]
|
|
11
|
-
[bool]$includeSelf = $false
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
$scope = if ($includeSelf) {
|
|
15
|
-
[TreeScope]::Element -bor [TreeScope]::Children
|
|
16
|
-
} else {
|
|
17
|
-
[TreeScope]::Children
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
$validChild = $element.FindFirst($scope, $condition)
|
|
21
|
-
|
|
22
|
-
if ($validChild -ne $null) {
|
|
23
|
-
return $validChild
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
$children = $element.FindAll([TreeScope]::Children, [Condition]::TrueCondition)
|
|
27
|
-
foreach ($child in $children) {
|
|
28
|
-
$result = Find-AllChildrenRecursively -element $child -condition $condition -returnFirstResult $true
|
|
29
|
-
if ($result -ne $null) {
|
|
30
|
-
return $result[0]
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return $null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function Find-AllChildrenRecursively {
|
|
38
|
-
param (
|
|
39
|
-
[Parameter(Mandatory=$true)]
|
|
40
|
-
[AutomationElement]$element,
|
|
41
|
-
[Parameter(Mandatory=$true)]
|
|
42
|
-
[Condition]$condition,
|
|
43
|
-
[bool]$returnFirstResult = $false,
|
|
44
|
-
[Parameter(Mandatory=$false)]
|
|
45
|
-
[bool]$includeSelf = $false
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
$children = $element.FindAll([TreeScope]::Children, [Condition]::TrueCondition)
|
|
49
|
-
$validChildren = @($children | Where-Object { $_.FindFirst([TreeScope]::Element, $condition) -ne $null })
|
|
50
|
-
|
|
51
|
-
if ($includeSelf) {
|
|
52
|
-
$self = $element.FindFirst([TreeScope]::Element, $condition)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if ($null -ne $self) {
|
|
56
|
-
$validChildren += $self
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
foreach ($child in $children) {
|
|
60
|
-
$Allresults = Find-AllChildrenRecursively -element $child -condition $condition
|
|
61
|
-
if ($returnFirstResult -and $Allresults.Count -gt 0) {
|
|
62
|
-
return $Allresults
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
foreach ($result in $Allresults) {
|
|
66
|
-
$validChildren += ($result | Where-Object { $_.FindFirst([TreeScope]::Element, $condition) -ne $null })
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return $validChildren
|
|
71
|
-
}
|
|
72
|
-
`;
|
|
73
|
-
|
|
74
|
-
export const PAGE_SOURCE = pwsh /* ps1 */ `
|
|
75
|
-
function Get-PageSource {
|
|
76
|
-
param (
|
|
77
|
-
[Parameter(Mandatory = $true)]
|
|
78
|
-
[AutomationElement]$element,
|
|
79
|
-
[Xml.XmlDocument]$xmlDoc,
|
|
80
|
-
[Xml.XmlElement]$xmlElement
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
$localizedControlType = $element.GetCurrentPropertyValue([AutomationElement]::LocalizedControlTypeProperty)
|
|
85
|
-
$controlType = $element.GetCurrentPropertyValue([AutomationElement]::ControlTypeProperty)
|
|
86
|
-
|
|
87
|
-
$tagName = ''
|
|
88
|
-
try {
|
|
89
|
-
$tagName = $controlType.ProgrammaticName.Split('.')[-1]
|
|
90
|
-
} catch {
|
|
91
|
-
# fallback to LocalizedControlType ControlType is empty
|
|
92
|
-
$tagName = -join ($localizedControlType -split ' ' | ForEach-Object {
|
|
93
|
-
$_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower()
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
$acceleratorKey = $element.GetCurrentPropertyValue([AutomationElement]::AcceleratorKeyProperty)
|
|
98
|
-
$accessKey = $element.GetCurrentPropertyValue([AutomationElement]::AccessKeyProperty)
|
|
99
|
-
$automationId = $element.GetCurrentPropertyValue([AutomationElement]::AutomationIdProperty)
|
|
100
|
-
$className = $element.GetCurrentPropertyValue([AutomationElement]::ClassNameProperty)
|
|
101
|
-
$frameworkId = $element.GetCurrentPropertyValue([AutomationElement]::FrameworkIdProperty)
|
|
102
|
-
$hasKeyboardfocus = $element.GetCurrentPropertyValue([AutomationElement]::HasKeyboardfocusProperty)
|
|
103
|
-
$helpText = $element.GetCurrentPropertyValue([AutomationElement]::HelpTextProperty)
|
|
104
|
-
$isContentelement = $element.GetCurrentPropertyValue([AutomationElement]::IsContentelementProperty)
|
|
105
|
-
$isControlelement = $element.GetCurrentPropertyValue([AutomationElement]::IsControlelementProperty)
|
|
106
|
-
$isEnabled = $element.GetCurrentPropertyValue([AutomationElement]::IsEnabledProperty)
|
|
107
|
-
$isKeyboardfocusable = $element.GetCurrentPropertyValue([AutomationElement]::IsKeyboardfocusableProperty)
|
|
108
|
-
$isOffscreen = $element.GetCurrentPropertyValue([AutomationElement]::IsOffscreenProperty)
|
|
109
|
-
$isPassword = $element.GetCurrentPropertyValue([AutomationElement]::IsPasswordProperty)
|
|
110
|
-
$isRequiredforform = $element.GetCurrentPropertyValue([AutomationElement]::IsRequiredforformProperty)
|
|
111
|
-
$itemStatus = $element.GetCurrentPropertyValue([AutomationElement]::ItemStatusProperty)
|
|
112
|
-
$itemType = $element.GetCurrentPropertyValue([AutomationElement]::ItemTypeProperty)
|
|
113
|
-
$name = $element.GetCurrentPropertyValue([AutomationElement]::NameProperty)
|
|
114
|
-
$orientation = $element.GetCurrentPropertyValue([AutomationElement]::OrientationProperty)
|
|
115
|
-
$processId = $element.GetCurrentPropertyValue([AutomationElement]::ProcessIdProperty)
|
|
116
|
-
$runtimeId = $element.GetCurrentPropertyValue([AutomationElement]::RuntimeIdProperty) -join '.'
|
|
117
|
-
$boundingRectangle = $element.Current.BoundingRectangle
|
|
118
|
-
$x = $boundingRectangle.X - $rootElement.Current.BoundingRectangle.X
|
|
119
|
-
$y = $boundingRectangle.Y - $rootElement.Current.BoundingRectangle.Y
|
|
120
|
-
$width = $boundingRectangle.Width
|
|
121
|
-
$height = $boundingRectangle.Height
|
|
122
|
-
|
|
123
|
-
if ($null -eq $xmlDoc) { $xmlDoc = [Xml.XmlDocument]::new() }
|
|
124
|
-
|
|
125
|
-
$newXmlElement = $xmlDoc.CreateElement($tagName)
|
|
126
|
-
$newXmlElement.SetAttribute("AcceleratorKey", $acceleratorKey)
|
|
127
|
-
$newXmlElement.SetAttribute("AccessKey", $accessKey)
|
|
128
|
-
$newXmlElement.SetAttribute("AutomationId", $automationId)
|
|
129
|
-
$newXmlElement.SetAttribute("ClassName", $className)
|
|
130
|
-
$newXmlElement.SetAttribute("FrameworkId", $frameworkId)
|
|
131
|
-
$newXmlElement.SetAttribute("HasKeyboardfocus", $hasKeyboardfocus)
|
|
132
|
-
$newXmlElement.SetAttribute("HelpText", $helpText)
|
|
133
|
-
$newXmlElement.SetAttribute("IsContentelement", $isContentelement)
|
|
134
|
-
$newXmlElement.SetAttribute("IsControlelement", $isControlelement)
|
|
135
|
-
$newXmlElement.SetAttribute("IsEnabled", $isEnabled)
|
|
136
|
-
$newXmlElement.SetAttribute("IsKeyboardfocusable", $isKeyboardfocusable)
|
|
137
|
-
$newXmlElement.SetAttribute("IsOffscreen", $isOffscreen)
|
|
138
|
-
$newXmlElement.SetAttribute("IsPassword", $isPassword)
|
|
139
|
-
$newXmlElement.SetAttribute("IsRequiredforform", $isRequiredforform)
|
|
140
|
-
$newXmlElement.SetAttribute("ItemStatus", $itemStatus)
|
|
141
|
-
$newXmlElement.SetAttribute("ItemType", $itemType)
|
|
142
|
-
$newXmlElement.SetAttribute("LocalizedControlType", $localizedControlType)
|
|
143
|
-
$newXmlElement.SetAttribute("Name", $name)
|
|
144
|
-
$newXmlElement.SetAttribute("Orientation", $orientation)
|
|
145
|
-
$newXmlElement.SetAttribute("ProcessId", $processId)
|
|
146
|
-
$newXmlElement.SetAttribute("RuntimeId", $runtimeId)
|
|
147
|
-
$newXmlElement.SetAttribute("x", $x)
|
|
148
|
-
$newXmlElement.SetAttribute("y", $y)
|
|
149
|
-
$newXmlElement.SetAttribute("width", $width)
|
|
150
|
-
$newXmlElement.SetAttribute("height", $height)
|
|
151
|
-
|
|
152
|
-
$pattern = $null
|
|
153
|
-
|
|
154
|
-
if ($element.TryGetCurrentPattern([WindowPattern]::Pattern, [ref]$pattern)) {
|
|
155
|
-
$newXmlElement.SetAttribute("CanMaximize", $windowPattern.Current.CanMaximize)
|
|
156
|
-
$newXmlElement.SetAttribute("CanMinimize", $windowPattern.Current.CanMinimize)
|
|
157
|
-
$newXmlElement.SetAttribute("IsModal", $windowPattern.Current.IsModal)
|
|
158
|
-
$newXmlElement.SetAttribute("WindowVisualState", $windowPattern.Current.WindowVisualState)
|
|
159
|
-
$newXmlElement.SetAttribute("WindowInteractionState", $windowPattern.Current.WindowInteractionState)
|
|
160
|
-
$newXmlElement.SetAttribute("IsTopmost", $windowPattern.Current.IsTopmost)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if ($element.TryGetCurrentPattern([TransformPattern]::Pattern, [ref]$pattern)) {
|
|
164
|
-
$newXmlElement.SetAttribute("CanRotate", $windowPattern.Current.CanRotate)
|
|
165
|
-
$newXmlElement.SetAttribute("CanResize", $windowPattern.Current.CanResize)
|
|
166
|
-
$newXmlElement.SetAttribute("CanMove", $windowPattern.Current.CanMove)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
# TODO: more to be added depending on the available patterns
|
|
170
|
-
|
|
171
|
-
if ($null -eq $xmlElement) {
|
|
172
|
-
$xmlElement = $xmlDoc.AppendChild($newXmlElement)
|
|
173
|
-
} else {
|
|
174
|
-
$xmlElement = $xmlElement.AppendChild($newXmlElement)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
$elementsToProcess = New-Object System.Collections.Queue
|
|
178
|
-
$element.FindAll([TreeScope]::Children, $cacheRequest.TreeFilter) | ForEach-Object {
|
|
179
|
-
$elementsToProcess.Enqueue($_)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
while ($elementsToProcess.Count -gt 0) {
|
|
183
|
-
$currentElement = $elementsToProcess.Dequeue()
|
|
184
|
-
Get-PageSource $currentElement $xmlDoc $xmlElement | Out-Null
|
|
185
|
-
}
|
|
186
|
-
} catch {
|
|
187
|
-
# noop
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return $xmlElement
|
|
191
|
-
}
|
|
192
|
-
`;
|
package/lib/commands/index.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import * as actions from './actions';
|
|
2
|
-
import * as powershell from './powershell';
|
|
3
|
-
import * as element from './element';
|
|
4
|
-
import * as extension from './extension';
|
|
5
|
-
import * as device from './device';
|
|
6
|
-
import * as system from './system';
|
|
7
|
-
import * as app from './app';
|
|
8
|
-
|
|
9
|
-
const commands = {
|
|
10
|
-
...actions,
|
|
11
|
-
...powershell,
|
|
12
|
-
...element,
|
|
13
|
-
...extension,
|
|
14
|
-
...system,
|
|
15
|
-
...device,
|
|
16
|
-
...app,
|
|
17
|
-
// add the rest of the commands here
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type Commands = {
|
|
21
|
-
[key in keyof typeof commands]: typeof commands[key];
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
declare module '../driver' {
|
|
25
|
-
interface NovaWindows2Driver extends Commands { }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export default commands;
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { NovaWindows2Driver } from '../driver';
|
|
3
|
-
import { errors } from '@appium/base-driver';
|
|
4
|
-
import { FIND_CHILDREN_RECURSIVELY, PAGE_SOURCE } from './functions';
|
|
5
|
-
|
|
6
|
-
const SET_UTF8_ENCODING = /* ps1 */ `$OutputEncoding = [Console]::OutputEncoding = [Text.Encoding]::UTF8`;
|
|
7
|
-
const ADD_NECESSARY_ASSEMBLIES = /* ps1 */ `Add-Type -AssemblyName UIAutomationClient; Add-Type -AssemblyName System.Drawing; Add-Type -AssemblyName PresentationCore; Add-Type -AssemblyName System.Windows.Forms`;
|
|
8
|
-
const USE_UI_AUTOMATION_CLIENT = /* ps1 */ `using namespace System.Windows.Automation`;
|
|
9
|
-
const INIT_CACHE_REQUEST = /* ps1 */ `($cacheRequest = New-Object System.Windows.Automation.CacheRequest).TreeFilter = [AndCondition]::new([Automation]::ControlViewCondition, [NotCondition]::new([PropertyCondition]::new([AutomationElement]::FrameworkIdProperty, 'Chrome'))); $cacheRequest.Push()`;
|
|
10
|
-
const INIT_ROOT_ELEMENT = /* ps1 */ `$rootElement = [AutomationElement]::RootElement`;
|
|
11
|
-
const NULL_ROOT_ELEMENT = /* ps1 */ `$rootElement = $null`;
|
|
12
|
-
const INIT_ELEMENT_TABLE = /* ps1 */ `$elementTable = New-Object System.Collections.Generic.Dictionary[[string]\`,[AutomationElement]]`;
|
|
13
|
-
|
|
14
|
-
async function executeRawCommand(driver: NovaWindows2Driver, command: string): Promise<string> {
|
|
15
|
-
const magicNumber = 0xF2EE;
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17
|
-
const powerShell = driver.powerShell!;
|
|
18
|
-
|
|
19
|
-
driver.powerShellStdOut = '';
|
|
20
|
-
driver.powerShellStdErr = '';
|
|
21
|
-
|
|
22
|
-
powerShell.stdin.write(`${command}\n`);
|
|
23
|
-
powerShell.stdin.write(/* ps1 */ `Write-Output $([char]0x${magicNumber.toString(16)})\n`);
|
|
24
|
-
|
|
25
|
-
return await new Promise<string>((resolve, reject) => {
|
|
26
|
-
const onClose = (code: number) => {
|
|
27
|
-
reject(new errors.UnknownError(`PowerShell process exited unexpectedly with code ${code}`));
|
|
28
|
-
driver.powerShell = undefined; // Clear the reference as the process is dead
|
|
29
|
-
};
|
|
30
|
-
powerShell.on('close', onClose);
|
|
31
|
-
|
|
32
|
-
const onData: Parameters<typeof powerShell.stdout.on>[1] = ((chunk: any) => {
|
|
33
|
-
const magicChar = String.fromCharCode(magicNumber);
|
|
34
|
-
if (chunk.toString().includes(magicChar)) {
|
|
35
|
-
powerShell.stdout.off('data', onData);
|
|
36
|
-
powerShell.off('close', onClose);
|
|
37
|
-
if (driver.powerShellStdErr) {
|
|
38
|
-
reject(new errors.UnknownError(driver.powerShellStdErr));
|
|
39
|
-
} else {
|
|
40
|
-
resolve(driver.powerShellStdOut.replace(`${magicChar}`, '').trim());
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}).bind(driver);
|
|
44
|
-
|
|
45
|
-
powerShell.stdout.on('data', onData);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function startPowerShellSession(this: NovaWindows2Driver): Promise<void> {
|
|
50
|
-
this.log.debug('Starting new PowerShell session...');
|
|
51
|
-
const powerShell = spawn('powershell.exe', ['-NoExit', '-Command', '-']);
|
|
52
|
-
powerShell.stdout.setEncoding('utf8');
|
|
53
|
-
powerShell.stderr.setEncoding('utf8');
|
|
54
|
-
|
|
55
|
-
powerShell.stdout.on('data', (chunk: any) => {
|
|
56
|
-
this.powerShellStdOut += chunk.toString();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
powerShell.stderr.on('data', (chunk: any) => {
|
|
60
|
-
this.powerShellStdErr += chunk.toString();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
this.powerShell = powerShell;
|
|
64
|
-
|
|
65
|
-
if (this.caps.appWorkingDir) {
|
|
66
|
-
const envVarsSet: Set<string> = new Set();
|
|
67
|
-
const matches = this.caps.appWorkingDir.matchAll(/%([^%]+)%/g);
|
|
68
|
-
|
|
69
|
-
for (const match of matches) {
|
|
70
|
-
envVarsSet.add(match[1]);
|
|
71
|
-
}
|
|
72
|
-
const envVars = Array.from(envVarsSet);
|
|
73
|
-
for (const envVar of envVars) {
|
|
74
|
-
this.caps.appWorkingDir = this.caps.appWorkingDir.replaceAll(`%${envVar}%`, process.env[envVar.toUpperCase()] ?? '');
|
|
75
|
-
}
|
|
76
|
-
// Use raw execution to bypass queue
|
|
77
|
-
await executeRawCommand(this, `Set-Location -Path '${this.caps.appWorkingDir}'`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Use raw execution to bypass queue
|
|
81
|
-
await executeRawCommand(this, SET_UTF8_ENCODING);
|
|
82
|
-
await executeRawCommand(this, ADD_NECESSARY_ASSEMBLIES);
|
|
83
|
-
await executeRawCommand(this, USE_UI_AUTOMATION_CLIENT);
|
|
84
|
-
await executeRawCommand(this, INIT_CACHE_REQUEST);
|
|
85
|
-
await executeRawCommand(this, INIT_ELEMENT_TABLE);
|
|
86
|
-
|
|
87
|
-
// initialize functions
|
|
88
|
-
await executeRawCommand(this, PAGE_SOURCE);
|
|
89
|
-
await executeRawCommand(this, FIND_CHILDREN_RECURSIVELY);
|
|
90
|
-
|
|
91
|
-
if ((!this.caps.app && !this.caps.appTopLevelWindow) || (!this.caps.app || this.caps.app.toLowerCase() === 'none')) {
|
|
92
|
-
this.log.info(`No app or top-level window specified in capabilities. Setting root element to null.`);
|
|
93
|
-
await executeRawCommand(this, NULL_ROOT_ELEMENT);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (this.caps.app && this.caps.app.toLowerCase() === 'root') {
|
|
97
|
-
this.log.info(`'root' specified as app in capabilities. Setting root element to desktop root.`);
|
|
98
|
-
await executeRawCommand(this, INIT_ROOT_ELEMENT);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (this.caps.app && this.caps.app.toLowerCase() !== 'none' && this.caps.app.toLowerCase() !== 'root') {
|
|
102
|
-
this.log.info(`Application path specified in capabilities: ${this.caps.app}`);
|
|
103
|
-
const envVarsSet: Set<string> = new Set();
|
|
104
|
-
const matches = this.caps.app.matchAll(/%([^%]+)%/g);
|
|
105
|
-
|
|
106
|
-
for (const match of matches) {
|
|
107
|
-
envVarsSet.add(match[1]);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const envVars = Array.from(envVarsSet);
|
|
111
|
-
this.log.info(`Detected the following environment variables in app path: ${envVars.map((envVar) => `%${envVar}%`).join(', ')}`);
|
|
112
|
-
|
|
113
|
-
for (const envVar of envVars) {
|
|
114
|
-
this.caps.app = this.caps.app.replaceAll(`%${envVar}%`, process.env[envVar.toUpperCase()] ?? '');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
await this.changeRootElement(this.caps.app);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (this.caps.appTopLevelWindow) {
|
|
121
|
-
const nativeWindowHandle = Number(this.caps.appTopLevelWindow);
|
|
122
|
-
|
|
123
|
-
if (isNaN(nativeWindowHandle)) {
|
|
124
|
-
throw new errors.InvalidArgumentError(`Invalid capabilities. Capability 'appTopLevelWindow' is not a valid native window handle.`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
await this.changeRootElement(nativeWindowHandle);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export async function sendIsolatedPowerShellCommand(this: NovaWindows2Driver, command: string): Promise<string> {
|
|
132
|
-
const magicNumber = 0xF2EE;
|
|
133
|
-
|
|
134
|
-
const powerShell = spawn('powershell.exe', ['-NoExit', '-Command', '-']);
|
|
135
|
-
try {
|
|
136
|
-
powerShell.stdout.setEncoding('utf8');
|
|
137
|
-
powerShell.stdout.setEncoding('utf8');
|
|
138
|
-
|
|
139
|
-
powerShell.stdout.on('data', (chunk: any) => {
|
|
140
|
-
this.powerShellStdOut += chunk.toString();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
powerShell.stderr.on('data', (chunk: any) => {
|
|
144
|
-
this.powerShellStdErr += chunk.toString();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const result = await new Promise<string>((resolve, reject) => {
|
|
148
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
149
|
-
const powerShell = this.powerShell!;
|
|
150
|
-
|
|
151
|
-
this.powerShellStdOut = '';
|
|
152
|
-
this.powerShellStdErr = '';
|
|
153
|
-
|
|
154
|
-
powerShell.stdin.write(`${SET_UTF8_ENCODING}\n`);
|
|
155
|
-
if (this.caps.appWorkingDir) {
|
|
156
|
-
const envVarsSet: Set<string> = new Set();
|
|
157
|
-
const matches = this.caps.appWorkingDir.matchAll(/%([^%]+)%/g);
|
|
158
|
-
|
|
159
|
-
for (const match of matches) {
|
|
160
|
-
envVarsSet.add(match[1]);
|
|
161
|
-
}
|
|
162
|
-
const envVars = Array.from(envVarsSet);
|
|
163
|
-
for (const envVar of envVars) {
|
|
164
|
-
this.caps.appWorkingDir = this.caps.appWorkingDir.replaceAll(`%${envVar}%`, process.env[envVar.toUpperCase()] ?? '');
|
|
165
|
-
}
|
|
166
|
-
powerShell.stdin.write(`Set-Location -Path '${this.caps.appWorkingDir}'\n`);
|
|
167
|
-
}
|
|
168
|
-
powerShell.stdin.write(`${command}\n`);
|
|
169
|
-
powerShell.stdin.write(/* ps1 */ `Write-Output $([char]0x${magicNumber.toString(16)})\n`);
|
|
170
|
-
|
|
171
|
-
const onClose = (code: number) => {
|
|
172
|
-
reject(new errors.UnknownError(`PowerShell process exited unexpectedly with code ${code}`));
|
|
173
|
-
};
|
|
174
|
-
powerShell.on('close', onClose);
|
|
175
|
-
|
|
176
|
-
const onData: Parameters<typeof powerShell.stdout.on>[1] = ((chunk: any) => {
|
|
177
|
-
const magicChar = String.fromCharCode(magicNumber);
|
|
178
|
-
if (chunk.toString().includes(magicChar)) {
|
|
179
|
-
powerShell.stdout.off('data', onData);
|
|
180
|
-
powerShell.off('close', onClose);
|
|
181
|
-
if (this.powerShellStdErr) {
|
|
182
|
-
reject(new errors.UnknownError(this.powerShellStdErr));
|
|
183
|
-
} else {
|
|
184
|
-
resolve(this.powerShellStdOut.replace(`${magicChar}`, '').trim());
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}).bind(this);
|
|
188
|
-
|
|
189
|
-
powerShell.stdout.on('data', onData);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// commented out for now to avoid cluttering the logs with long command outputs
|
|
193
|
-
// this.log.debug(`PowerShell command executed:\n${command}\n\nCommand output below:\n${result}\n --------`);
|
|
194
|
-
|
|
195
|
-
return result;
|
|
196
|
-
} finally {
|
|
197
|
-
// Ensure the isolated PowerShell process is terminated
|
|
198
|
-
try {
|
|
199
|
-
powerShell.kill();
|
|
200
|
-
} catch (e) {
|
|
201
|
-
this.log.warn(`Failed to terminate isolated PowerShell process: ${e}`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export async function sendPowerShellCommand(this: NovaWindows2Driver, command: string): Promise<string> {
|
|
207
|
-
const nextCommand = async () => {
|
|
208
|
-
if (!this.powerShell) {
|
|
209
|
-
this.log.warn('PowerShell session not running. It was either closed or has crashed. Attempting to start a new session...');
|
|
210
|
-
await this.startPowerShellSession();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Use the extracted raw command function
|
|
214
|
-
return await executeRawCommand(this, command);
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
// Chain the command to the queue
|
|
218
|
-
// Use .catch to ignore previous failures, then .then to queue this command.
|
|
219
|
-
// This prevents re-running this command if it fails (no infinite loop).
|
|
220
|
-
this.commandQueue = this.commandQueue
|
|
221
|
-
.catch(() => { /* ignore previous error */ })
|
|
222
|
-
.then(nextCommand);
|
|
223
|
-
|
|
224
|
-
return this.commandQueue;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export async function terminatePowerShellSession(this: NovaWindows2Driver): Promise<void> {
|
|
228
|
-
if (!this.powerShell) {
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (this.powerShell.exitCode !== null) {
|
|
233
|
-
this.log.debug(`PowerShell session already terminated.`);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
this.log.debug(`Terminating PowerShell session...`);
|
|
238
|
-
const waitForClose = new Promise<void>((resolve, reject) => {
|
|
239
|
-
if (!this.powerShell) {
|
|
240
|
-
resolve();
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
this.powerShell?.once('close', () => {
|
|
244
|
-
resolve();
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
this.powerShell?.once('error', (err: Error) => {
|
|
248
|
-
reject(err);
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.powerShell.kill();
|
|
254
|
-
await waitForClose;
|
|
255
|
-
this.log.debug(`PowerShell session terminated successfully.`);
|
|
256
|
-
}
|
package/lib/commands/system.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { Orientation } from '@appium/types';
|
|
2
|
-
import { NovaWindows2Driver } from '../driver';
|
|
3
|
-
import { getDisplayOrientation } from '../winapi/user32';
|
|
4
|
-
|
|
5
|
-
export function getOrientation(this: NovaWindows2Driver): Orientation {
|
|
6
|
-
return getDisplayOrientation();
|
|
7
|
-
}
|
package/lib/constants.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const POWER_SHELL_FEATURE = 'power_shell';
|
package/lib/constraints.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { Constraints } from '@appium/types';
|
|
2
|
-
|
|
3
|
-
export const UI_AUTOMATION_DRIVER_CONSTRAINTS = {
|
|
4
|
-
platformName: {
|
|
5
|
-
isString: true,
|
|
6
|
-
inclusionCaseInsensitive: ['Windows'],
|
|
7
|
-
presence: true,
|
|
8
|
-
},
|
|
9
|
-
smoothPointerMove: {
|
|
10
|
-
isString: true,
|
|
11
|
-
},
|
|
12
|
-
delayBeforeClick: {
|
|
13
|
-
isNumber: true,
|
|
14
|
-
},
|
|
15
|
-
delayAfterClick: {
|
|
16
|
-
isNumber: true,
|
|
17
|
-
},
|
|
18
|
-
appTopLevelWindow: {
|
|
19
|
-
isString: true,
|
|
20
|
-
},
|
|
21
|
-
shouldCloseApp: {
|
|
22
|
-
isBoolean: true,
|
|
23
|
-
},
|
|
24
|
-
appArguments: {
|
|
25
|
-
isString: true,
|
|
26
|
-
},
|
|
27
|
-
appWorkingDir: {
|
|
28
|
-
isString: true,
|
|
29
|
-
},
|
|
30
|
-
prerun: {
|
|
31
|
-
isObject: true,
|
|
32
|
-
},
|
|
33
|
-
postrun: {
|
|
34
|
-
isObject: true,
|
|
35
|
-
},
|
|
36
|
-
isolatedScriptExecution: {
|
|
37
|
-
isBoolean: true,
|
|
38
|
-
}
|
|
39
|
-
} as const satisfies Constraints;
|
|
40
|
-
|
|
41
|
-
export default UI_AUTOMATION_DRIVER_CONSTRAINTS;
|
|
42
|
-
|
|
43
|
-
export type NovaWindowsDriverConstraints = typeof UI_AUTOMATION_DRIVER_CONSTRAINTS;
|