appium-desktop-driver 1.7.2-preview.1 → 1.7.2
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/README.md +2 -0
- package/build/eslint.config.d.mts +3 -0
- package/build/eslint.config.d.mts.map +1 -0
- package/build/eslint.config.mjs +22 -0
- package/build/eslint.config.mjs.map +1 -0
- package/build/lib/commands/actions.d.ts +3 -5
- package/build/lib/commands/actions.d.ts.map +1 -1
- package/build/lib/commands/actions.js +91 -83
- package/build/lib/commands/actions.js.map +1 -1
- package/build/lib/commands/app.d.ts +1 -53
- package/build/lib/commands/app.d.ts.map +1 -1
- package/build/lib/commands/app.js +147 -415
- package/build/lib/commands/app.js.map +1 -1
- package/build/lib/commands/device.d.ts +8 -0
- package/build/lib/commands/device.d.ts.map +1 -1
- package/build/lib/commands/device.js +229 -4
- package/build/lib/commands/device.js.map +1 -1
- package/build/lib/commands/element.d.ts +3 -0
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +53 -102
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/extension.d.ts +0 -2
- package/build/lib/commands/extension.d.ts.map +1 -1
- package/build/lib/commands/extension.js +184 -111
- package/build/lib/commands/extension.js.map +1 -1
- package/build/lib/commands/index.d.ts +17 -28
- package/build/lib/commands/index.d.ts.map +1 -1
- package/build/lib/commands/index.js +2 -4
- package/build/lib/commands/index.js.map +1 -1
- 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 +202 -0
- package/build/lib/commands/powershell.js.map +1 -0
- package/build/lib/commands/screen-recorder.d.ts +3 -3
- package/build/lib/commands/screen-recorder.d.ts.map +1 -1
- package/build/lib/commands/screen-recorder.js +24 -33
- package/build/lib/commands/screen-recorder.js.map +1 -1
- package/build/lib/constraints.d.ts +7 -26
- package/build/lib/constraints.d.ts.map +1 -1
- package/build/lib/constraints.js +6 -25
- package/build/lib/constraints.js.map +1 -1
- package/build/lib/driver.d.ts +13 -22
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +49 -130
- package/build/lib/driver.js.map +1 -1
- package/build/lib/mcp/tools/index.d.ts.map +1 -1
- package/build/lib/mcp/tools/index.js +0 -2
- package/build/lib/mcp/tools/index.js.map +1 -1
- package/build/lib/powershell/conditions.d.ts.map +1 -1
- package/build/lib/powershell/conditions.js +0 -7
- package/build/lib/powershell/conditions.js.map +1 -1
- package/build/lib/util.d.ts +1 -11
- package/build/lib/util.d.ts.map +1 -1
- package/build/lib/util.js +7 -158
- package/build/lib/util.js.map +1 -1
- package/build/lib/winapi/user32.d.ts +6 -5
- package/build/lib/winapi/user32.d.ts.map +1 -1
- package/build/lib/winapi/user32.js +88 -131
- package/build/lib/winapi/user32.js.map +1 -1
- package/build/lib/xpath/core.d.ts +4 -10
- package/build/lib/xpath/core.d.ts.map +1 -1
- package/build/lib/xpath/core.js +224 -205
- package/build/lib/xpath/core.js.map +1 -1
- package/build/lib/xpath/functions.d.ts +2 -7
- package/build/lib/xpath/functions.d.ts.map +1 -1
- package/build/lib/xpath/functions.js +25 -49
- package/build/lib/xpath/functions.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +12 -18
- package/CHANGELOG.md +0 -249
- package/build/lib/commands/contexts.d.ts +0 -64
- package/build/lib/commands/contexts.d.ts.map +0 -1
- package/build/lib/commands/contexts.js +0 -166
- package/build/lib/commands/contexts.js.map +0 -1
- package/build/lib/commands/server-session.d.ts +0 -9
- package/build/lib/commands/server-session.d.ts.map +0 -1
- package/build/lib/commands/server-session.js +0 -158
- package/build/lib/commands/server-session.js.map +0 -1
- package/build/lib/log-file.d.ts +0 -10
- package/build/lib/log-file.d.ts.map +0 -1
- package/build/lib/log-file.js +0 -86
- package/build/lib/log-file.js.map +0 -1
- package/build/lib/mcp/tools/context.d.ts +0 -4
- package/build/lib/mcp/tools/context.d.ts.map +0 -1
- package/build/lib/mcp/tools/context.js +0 -49
- package/build/lib/mcp/tools/context.js.map +0 -1
- package/build/lib/server/client.d.ts +0 -15
- package/build/lib/server/client.d.ts.map +0 -1
- package/build/lib/server/client.js +0 -169
- package/build/lib/server/client.js.map +0 -1
- package/build/lib/server/conditions.d.ts +0 -13
- package/build/lib/server/conditions.d.ts.map +0 -1
- package/build/lib/server/conditions.js +0 -71
- package/build/lib/server/conditions.js.map +0 -1
- package/build/lib/server/converter-bridge.d.ts +0 -26
- package/build/lib/server/converter-bridge.d.ts.map +0 -1
- package/build/lib/server/converter-bridge.js +0 -171
- package/build/lib/server/converter-bridge.js.map +0 -1
- package/build/lib/server/index.d.ts +0 -5
- package/build/lib/server/index.d.ts.map +0 -1
- package/build/lib/server/index.js +0 -25
- package/build/lib/server/index.js.map +0 -1
- package/build/lib/server/protocol.d.ts +0 -40
- package/build/lib/server/protocol.d.ts.map +0 -1
- package/build/lib/server/protocol.js +0 -3
- package/build/lib/server/protocol.js.map +0 -1
- package/build/lib/version.d.ts +0 -2
- package/build/lib/version.d.ts.map +0 -1
- package/build/lib/version.js +0 -20
- package/build/lib/version.js.map +0 -1
- package/scripts/postinstall.js +0 -39
|
@@ -12,141 +12,123 @@ exports.changeRootElement = changeRootElement;
|
|
|
12
12
|
exports.back = back;
|
|
13
13
|
exports.forward = forward;
|
|
14
14
|
exports.title = title;
|
|
15
|
-
exports.maximizeWindow = maximizeWindow;
|
|
16
|
-
exports.minimizeWindow = minimizeWindow;
|
|
17
15
|
exports.setWindowRect = setWindowRect;
|
|
18
|
-
exports.waitForNewWindow = waitForNewWindow;
|
|
19
|
-
exports.attachToWindowHandles = attachToWindowHandles;
|
|
20
16
|
exports.attachToApplicationWindow = attachToApplicationWindow;
|
|
21
|
-
exports.waitForMainWindow = waitForMainWindow;
|
|
22
17
|
const node_path_1 = require("node:path");
|
|
23
|
-
const
|
|
18
|
+
const powershell_1 = require("../powershell");
|
|
24
19
|
const util_1 = require("../util");
|
|
25
20
|
const base_driver_1 = require("@appium/base-driver");
|
|
26
21
|
const user32_1 = require("../winapi/user32");
|
|
27
22
|
const enums_1 = require("../enums");
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
* changeRootElement (outer loop)
|
|
36
|
-
* → attachToApplicationWindow
|
|
37
|
-
* → waitForNewWindow (polls for Win32 window handle)
|
|
38
|
-
* → findElement loop (polls UIAutomation tree for the element)
|
|
39
|
-
*
|
|
40
|
-
* At 500ms per poll, 10 failed attempts in any one phase cost 5 seconds,
|
|
41
|
-
* and the phases compound. At 200ms the same 10 attempts cost 2 seconds,
|
|
42
|
-
* cutting overall launch latency from 10-15s down to ~3-5s for typical
|
|
43
|
-
* UWP apps like Calculator.
|
|
44
|
-
*/
|
|
45
|
-
const POLL_INTERVAL_MS = 200;
|
|
46
|
-
/**
|
|
47
|
-
* Maximum number of polling attempts per retry phase.
|
|
48
|
-
* Combined with POLL_INTERVAL_MS this gives a ~6s window per phase,
|
|
49
|
-
* which is enough for most apps to register in the UIAutomation tree.
|
|
50
|
-
*/
|
|
51
|
-
const MAX_POLL_ATTEMPTS = 30;
|
|
52
|
-
// setWindow is called often during a session. If a handle doesn't exist,
|
|
53
|
-
// we fail fast (~400ms) instead of waiting 6s. Session-attach loops below
|
|
54
|
-
// still use MAX_POLL_ATTEMPTS.
|
|
55
|
-
const SET_WINDOW_MAX_POLL_ATTEMPTS = 2;
|
|
56
|
-
/**
|
|
57
|
-
* Normalizes the `ms:waitForAppLaunch` capability to milliseconds.
|
|
58
|
-
*
|
|
59
|
-
* WinAppDriver's spec says the value is in seconds (max 50), but many users
|
|
60
|
-
* pass milliseconds (e.g. 120000 meaning "120 seconds"). We auto-detect:
|
|
61
|
-
* values > 120 are assumed to already be in milliseconds; values ≤ 120 are
|
|
62
|
-
* treated as seconds and converted to ms.
|
|
63
|
-
*/
|
|
64
|
-
function normalizeWaitForAppLaunchMs(raw) {
|
|
65
|
-
if (raw == null || raw <= 0) {
|
|
66
|
-
return 0;
|
|
23
|
+
const GET_PAGE_SOURCE_COMMAND = (0, powershell_1.pwsh$ /* ps1 */) `
|
|
24
|
+
$el = ${0}
|
|
25
|
+
|
|
26
|
+
if ($el -eq $null) {
|
|
27
|
+
$dummy = [xml]'<DummyRoot></DummyRoot>'
|
|
28
|
+
return $dummy.OuterXml
|
|
67
29
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
30
|
+
|
|
31
|
+
Get-PageSource $el |
|
|
32
|
+
ForEach-Object { $_.OuterXml }
|
|
33
|
+
`;
|
|
34
|
+
const GET_SCREENSHOT_COMMAND = (0, powershell_1.pwsh /* ps1 */) `
|
|
35
|
+
if ($rootElement -eq $null) {
|
|
36
|
+
$bitmap = New-Object Drawing.Bitmap 1,1
|
|
37
|
+
$stream = New-Object IO.MemoryStream
|
|
38
|
+
$bitmap.Save($stream, [Drawing.Imaging.ImageFormat]::Png)
|
|
39
|
+
$bitmap.Dispose()
|
|
40
|
+
return [Convert]::ToBase64String($stream.ToArray())
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
$rect = $rootElement.Current.BoundingRectangle
|
|
44
|
+
$bitmap = New-Object Drawing.Bitmap([int32]$rect.Width, [int32]$rect.Height)
|
|
45
|
+
|
|
46
|
+
$graphics = [Drawing.Graphics]::FromImage($bitmap)
|
|
47
|
+
$graphics.CopyFromScreen([int32]$rect.Left, [int32]$rect.Top, 0, 0, $bitmap.Size)
|
|
48
|
+
$graphics.Dispose()
|
|
49
|
+
|
|
50
|
+
$stream = New-Object IO.MemoryStream
|
|
51
|
+
$bitmap.Save($stream, [Drawing.Imaging.ImageFormat]::Png)
|
|
52
|
+
$bitmap.Dispose()
|
|
53
|
+
|
|
54
|
+
[Convert]::ToBase64String($stream.ToArray())
|
|
55
|
+
`;
|
|
56
|
+
const SLEEP_INTERVAL_MS = 500;
|
|
72
57
|
async function getPageSource() {
|
|
73
|
-
return await this.
|
|
58
|
+
return await this.sendPowerShellCommand(GET_PAGE_SOURCE_COMMAND.format(powershell_1.AutomationElement.automationRoot));
|
|
74
59
|
}
|
|
75
60
|
async function getScreenshot() {
|
|
76
|
-
const automationRootId = await this.
|
|
61
|
+
const automationRootId = await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildCommand());
|
|
77
62
|
if (this.caps.app && this.caps.app.toLowerCase() !== 'root') {
|
|
78
63
|
try {
|
|
79
64
|
await this.focusElement({
|
|
80
|
-
[base_driver_1.W3C_ELEMENT_KEY]: automationRootId
|
|
65
|
+
[base_driver_1.W3C_ELEMENT_KEY]: automationRootId.trim(),
|
|
81
66
|
});
|
|
82
67
|
}
|
|
83
68
|
catch {
|
|
84
69
|
// noop
|
|
85
70
|
}
|
|
86
71
|
}
|
|
87
|
-
return await this.
|
|
72
|
+
return await this.sendPowerShellCommand(GET_SCREENSHOT_COMMAND);
|
|
88
73
|
}
|
|
89
74
|
async function getWindowRect() {
|
|
90
|
-
|
|
75
|
+
const result = await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildGetElementRectCommand());
|
|
76
|
+
return JSON.parse(result.replaceAll(/(?:infinity)/gi, 0x7FFFFFFF.toString()));
|
|
91
77
|
}
|
|
92
78
|
async function getWindowHandle() {
|
|
93
|
-
const
|
|
94
|
-
const nativeWindowHandle = await this.sendCommand('getProperty', { elementId: rootId, property: 'NativeWindowHandle' });
|
|
79
|
+
const nativeWindowHandle = await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildGetPropertyCommand(powershell_1.Property.NATIVE_WINDOW_HANDLE));
|
|
95
80
|
return `0x${Number(nativeWindowHandle).toString(16).padStart(8, '0')}`;
|
|
96
81
|
}
|
|
97
82
|
async function getWindowHandles() {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
83
|
+
if (this.appProcessIds.length > 0 && !this.caps.returnAllWindowHandles) {
|
|
84
|
+
const handles = (0, user32_1.getWindowAllHandlesForProcessIds)(this.appProcessIds);
|
|
85
|
+
return handles.map((h) => `0x${h.toString(16).padStart(8, '0')}`);
|
|
86
|
+
}
|
|
87
|
+
const result = await this.sendPowerShellCommand(powershell_1.AutomationElement.rootElement.findAll(powershell_1.TreeScope.CHILDREN, new powershell_1.TrueCondition()).buildCommand());
|
|
88
|
+
const elIds = result.split('\n').map((x) => x.trim()).filter(Boolean);
|
|
104
89
|
const nativeWindowHandles = [];
|
|
105
|
-
for (const elId of elIds
|
|
106
|
-
const nativeWindowHandle = await this.
|
|
90
|
+
for (const elId of elIds) {
|
|
91
|
+
const nativeWindowHandle = await this.sendPowerShellCommand(new powershell_1.FoundAutomationElement(elId).buildGetPropertyCommand(powershell_1.Property.NATIVE_WINDOW_HANDLE));
|
|
107
92
|
nativeWindowHandles.push(`0x${Number(nativeWindowHandle).toString(16).padStart(8, '0')}`);
|
|
108
93
|
}
|
|
109
94
|
return nativeWindowHandles;
|
|
110
95
|
}
|
|
111
96
|
async function setWindow(nameOrHandle) {
|
|
112
97
|
const handle = Number(nameOrHandle);
|
|
113
|
-
|
|
98
|
+
const maxRetries = this.caps['ms:windowSwitchRetries'] ?? 20;
|
|
99
|
+
const sleepInterval = this.caps['ms:windowSwitchInterval'] ?? SLEEP_INTERVAL_MS;
|
|
100
|
+
for (let i = 1; i <= maxRetries; i++) {
|
|
114
101
|
if (!isNaN(handle)) {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
120
|
-
if (elementId && elementId.trim() !== '') {
|
|
121
|
-
await this.sendCommand('setRootElementFromElementId', { elementId });
|
|
102
|
+
const condition = new powershell_1.PropertyCondition(powershell_1.Property.NATIVE_WINDOW_HANDLE, new powershell_1.PSInt32(handle));
|
|
103
|
+
const elementId = await this.sendPowerShellCommand(powershell_1.AutomationElement.rootElement.findFirst(powershell_1.TreeScope.CHILDREN_OR_SELF, condition).buildCommand());
|
|
104
|
+
if (elementId.trim() !== '') {
|
|
105
|
+
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new powershell_1.FoundAutomationElement(elementId).buildCommand()}`);
|
|
122
106
|
(0, user32_1.trySetForegroundWindow)(handle);
|
|
123
107
|
return;
|
|
124
108
|
}
|
|
125
109
|
}
|
|
126
110
|
const name = nameOrHandle;
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
contextElementId: null,
|
|
131
|
-
});
|
|
132
|
-
if (elementId && elementId.trim() !== '') {
|
|
111
|
+
const condition = new powershell_1.PropertyCondition(powershell_1.Property.NAME, new powershell_1.PSString(name));
|
|
112
|
+
const elementId = await this.sendPowerShellCommand(powershell_1.AutomationElement.rootElement.findFirst(powershell_1.TreeScope.CHILDREN, condition).buildCommand());
|
|
113
|
+
if (elementId.trim() !== '') {
|
|
133
114
|
this.log.info(`Found window with name '${name}'. Setting it as the root element.`);
|
|
134
|
-
await this.
|
|
115
|
+
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new powershell_1.FoundAutomationElement(elementId).buildCommand()}`);
|
|
135
116
|
(0, user32_1.trySetForegroundWindow)(handle);
|
|
136
117
|
return;
|
|
137
118
|
}
|
|
138
|
-
this.log.info(`Failed to locate window with name '${name}'. Sleeping for ${
|
|
139
|
-
await (0, util_1.sleep)(
|
|
119
|
+
this.log.info(`Failed to locate window with name '${name}'. Sleeping for ${sleepInterval} milliseconds and retrying... (${i}/${maxRetries})`);
|
|
120
|
+
await (0, util_1.sleep)(sleepInterval);
|
|
140
121
|
}
|
|
141
122
|
throw new base_driver_1.errors.NoSuchWindowError(`No window was found with name or handle '${nameOrHandle}'.`);
|
|
142
123
|
}
|
|
143
124
|
async function closeApp() {
|
|
144
|
-
const
|
|
145
|
-
|
|
125
|
+
const result = await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildCommand());
|
|
126
|
+
const elementId = result.split('\n').map((id) => id.trim()).filter(Boolean)[0];
|
|
127
|
+
if (!elementId) {
|
|
146
128
|
throw new base_driver_1.errors.NoSuchWindowError('No active app window is found for this session.');
|
|
147
129
|
}
|
|
148
|
-
await this.
|
|
149
|
-
await this.
|
|
130
|
+
await this.sendPowerShellCommand(new powershell_1.FoundAutomationElement(elementId).buildCloseCommand());
|
|
131
|
+
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = $null`);
|
|
150
132
|
}
|
|
151
133
|
async function launchApp() {
|
|
152
134
|
if (!this.caps.app || ['root', 'none'].includes(this.caps.app.toLowerCase())) {
|
|
@@ -157,103 +139,55 @@ async function launchApp() {
|
|
|
157
139
|
async function changeRootElement(pathOrNativeWindowHandle) {
|
|
158
140
|
if (typeof pathOrNativeWindowHandle === 'number') {
|
|
159
141
|
const nativeWindowHandle = pathOrNativeWindowHandle;
|
|
160
|
-
const
|
|
161
|
-
|
|
142
|
+
const condition = new powershell_1.PropertyCondition(powershell_1.Property.NATIVE_WINDOW_HANDLE, new powershell_1.PSInt32(nativeWindowHandle));
|
|
143
|
+
const elementId = await this.sendPowerShellCommand(powershell_1.AutomationElement.rootElement.findFirst(powershell_1.TreeScope.CHILDREN_OR_SELF, condition).buildCommand());
|
|
144
|
+
if (elementId.trim() !== '') {
|
|
145
|
+
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new powershell_1.FoundAutomationElement(elementId).buildCommand()}`);
|
|
162
146
|
(0, user32_1.trySetForegroundWindow)(nativeWindowHandle);
|
|
147
|
+
const pidResult = await this.sendPowerShellCommand(`$rootElement.Current.ProcessId`);
|
|
148
|
+
const pid = Number(pidResult.trim());
|
|
149
|
+
if (!isNaN(pid) && pid > 0) {
|
|
150
|
+
this.appProcessIds = [pid];
|
|
151
|
+
}
|
|
163
152
|
return;
|
|
164
153
|
}
|
|
165
154
|
throw new base_driver_1.errors.UnknownError('Failed to locate top level window with that window handle.');
|
|
166
155
|
}
|
|
167
156
|
const path = pathOrNativeWindowHandle;
|
|
168
|
-
|
|
169
|
-
const waitForAppLaunchMs = normalizeWaitForAppLaunchMs(this.caps['ms:waitForAppLaunch']);
|
|
170
|
-
// When ms:waitForAppLaunch is set, use it as an overall deadline for
|
|
171
|
-
// all retry loops. When not set, fall back to fixed iteration counts.
|
|
172
|
-
const deadline = waitForAppLaunchMs ? Date.now() + waitForAppLaunchMs : null;
|
|
173
|
-
if (isUwp) {
|
|
157
|
+
if ((0, util_1.isUwpAppId)(path)) {
|
|
174
158
|
this.log.debug('Detected app path to be in the UWP format.');
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
arguments: `shell:AppsFolder\\${path}`,
|
|
185
|
-
});
|
|
186
|
-
// Outer retry loop: polls until the app window is found or the
|
|
187
|
-
// deadline / attempt limit is reached. Each iteration is one
|
|
188
|
-
// POLL_INTERVAL_MS tick — no blind sleep up front.
|
|
189
|
-
for (let i = 1; deadline ? Date.now() < deadline : i <= 10; i++) {
|
|
190
|
-
// Strategy 1: new window detection.
|
|
191
|
-
// Works for WinUI 3 / standalone packaged apps that spawn a new
|
|
192
|
-
// top-level window in their own process.
|
|
193
|
-
const newHandles = (0, user32_1.getAllWindowHandles)().filter((h) => !handlesBeforeLaunch.has(h));
|
|
194
|
-
if (newHandles.length > 0) {
|
|
195
|
-
this.log.debug(`Found ${newHandles.length} new window(s) since launch: ${newHandles.map((h) => `0x${h.toString(16).padStart(8, '0')}`).join(', ')}`);
|
|
196
|
-
try {
|
|
197
|
-
if (await this.attachToWindowHandles(newHandles)) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
catch (err) {
|
|
202
|
-
if (err instanceof Error) {
|
|
203
|
-
this.log.debug(`New-window strategy failed: ${err.message}`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// Strategy 2: ApplicationFrameHost.
|
|
208
|
-
// Works for legacy hosted UWP apps. Prefer newly appeared
|
|
209
|
-
// ApplicationFrameHost windows (i.e. the frame that opened for
|
|
210
|
-
// this specific launch) to avoid accidentally attaching to a
|
|
211
|
-
// pre-existing frame that belongs to another UWP app.
|
|
212
|
-
const processIds = await this.sendCommand('getProcessIds', { processName: 'ApplicationFrameHost' });
|
|
213
|
-
if (processIds.length > 0) {
|
|
214
|
-
const afhHandles = (0, user32_1.getWindowAllHandlesForProcessIds)(processIds);
|
|
215
|
-
const newAfhHandles = afhHandles.filter((h) => !handlesBeforeLaunch.has(h));
|
|
216
|
-
const handlesToTry = newAfhHandles.length > 0 ? newAfhHandles : afhHandles;
|
|
217
|
-
this.log.debug(`ApplicationFrameHost strategy: trying ${handlesToTry.length} handle(s).`);
|
|
218
|
-
try {
|
|
219
|
-
if (await this.attachToWindowHandles(handlesToTry)) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
catch (err) {
|
|
224
|
-
if (err instanceof Error) {
|
|
225
|
-
this.log.debug(`ApplicationFrameHost strategy failed: ${err.message}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
this.log.info(`Failed to locate window of the app. Sleeping for ${POLL_INTERVAL_MS}ms and retrying... (attempt ${i})`);
|
|
230
|
-
await (0, util_1.sleep)(POLL_INTERVAL_MS);
|
|
159
|
+
await this.sendPowerShellCommand(/* ps1 */ `Start-Process 'explorer.exe' 'shell:AppsFolder\\${path}'${this.caps.appArguments ? ` -ArgumentList '${this.caps.appArguments}'` : ''}`);
|
|
160
|
+
const result = await this.sendPowerShellCommand(/* ps1 */ `(Get-Process -Name 'ApplicationFrameHost').Id`);
|
|
161
|
+
const processIds = result.split('\n').map((pid) => pid.trim()).filter(Boolean).map(Number);
|
|
162
|
+
this.log.debug(`Process IDs of ApplicationFrameHost processes (${processIds.length}): ` + processIds.join(', '));
|
|
163
|
+
this.appProcessIds = processIds;
|
|
164
|
+
await this.attachToApplicationWindow(processIds);
|
|
165
|
+
const attachedPid = Number((await this.sendPowerShellCommand(`$rootElement.Current.ProcessId`)).trim());
|
|
166
|
+
if (!isNaN(attachedPid) && attachedPid > 0) {
|
|
167
|
+
this.appProcessIds = [attachedPid];
|
|
231
168
|
}
|
|
232
169
|
}
|
|
233
170
|
else {
|
|
234
171
|
this.log.debug('Detected app path to be in the classic format.');
|
|
235
172
|
const normalizedPath = (0, node_path_1.normalize)(path);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
173
|
+
await this.sendPowerShellCommand(/* ps1 */ `Start-Process '${normalizedPath}'${this.caps.appArguments ? ` -ArgumentList '${this.caps.appArguments}'` : ''}`);
|
|
174
|
+
const breadcrumbs = normalizedPath.toLowerCase().split('\\').flatMap((x) => x.split('/'));
|
|
175
|
+
const executable = breadcrumbs[breadcrumbs.length - 1];
|
|
176
|
+
const processName = executable.endsWith('.exe') ? executable.slice(0, executable.length - 4) : executable;
|
|
177
|
+
const result = await this.sendPowerShellCommand(/* ps1 */ `(Get-Process -Name '${processName}' | Sort-Object StartTime -Descending).Id`);
|
|
178
|
+
const processIds = result.split('\n').map((pid) => pid.trim()).filter(Boolean).map(Number);
|
|
179
|
+
this.log.debug(`Process IDs of '${processName}' processes: ` + processIds.join(', '));
|
|
180
|
+
this.appProcessIds = processIds;
|
|
181
|
+
await this.attachToApplicationWindow(processIds);
|
|
182
|
+
const attachedPid = Number((await this.sendPowerShellCommand(`$rootElement.Current.ProcessId`)).trim());
|
|
183
|
+
if (!isNaN(attachedPid) && attachedPid > 0) {
|
|
184
|
+
this.appProcessIds = [attachedPid];
|
|
244
185
|
}
|
|
245
|
-
const result = await this.attachToApplicationWindow(launcherPid, { deadline });
|
|
246
|
-
if (!result.focused && deadline && Date.now() < deadline) {
|
|
247
|
-
this.log.info('Attached to a window that cannot receive focus (likely a splash screen). Waiting for the main window...');
|
|
248
|
-
await this.waitForMainWindow(result.knownPids, deadline);
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
186
|
}
|
|
252
|
-
throw new base_driver_1.errors.UnknownError('Failed to locate window of the app.');
|
|
253
187
|
}
|
|
254
188
|
async function back() {
|
|
255
|
-
const
|
|
256
|
-
if (!
|
|
189
|
+
const elementId = (await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildCommand())).trim();
|
|
190
|
+
if (!elementId) {
|
|
257
191
|
throw new base_driver_1.errors.NoSuchWindowError('No active window found for this session.');
|
|
258
192
|
}
|
|
259
193
|
(0, user32_1.keyDown)(enums_1.Key.ALT);
|
|
@@ -262,8 +196,8 @@ async function back() {
|
|
|
262
196
|
(0, user32_1.keyUp)(enums_1.Key.ALT);
|
|
263
197
|
}
|
|
264
198
|
async function forward() {
|
|
265
|
-
const
|
|
266
|
-
if (!
|
|
199
|
+
const elementId = (await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildCommand())).trim();
|
|
200
|
+
if (!elementId) {
|
|
267
201
|
throw new base_driver_1.errors.NoSuchWindowError('No active window found for this session.');
|
|
268
202
|
}
|
|
269
203
|
(0, user32_1.keyDown)(enums_1.Key.ALT);
|
|
@@ -272,27 +206,11 @@ async function forward() {
|
|
|
272
206
|
(0, user32_1.keyUp)(enums_1.Key.ALT);
|
|
273
207
|
}
|
|
274
208
|
async function title() {
|
|
275
|
-
const
|
|
276
|
-
if (!rootId) {
|
|
277
|
-
throw new base_driver_1.errors.NoSuchWindowError('No active window found for this session.');
|
|
278
|
-
}
|
|
279
|
-
return await this.sendCommand('getProperty', { elementId: rootId, property: 'Name' });
|
|
280
|
-
}
|
|
281
|
-
async function maximizeWindow() {
|
|
282
|
-
const elementId = (await this.sendCommand('saveRootElementToTable', {}))?.trim();
|
|
283
|
-
if (!elementId) {
|
|
284
|
-
throw new base_driver_1.errors.NoSuchWindowError('No active window found for this session.');
|
|
285
|
-
}
|
|
286
|
-
await this.sendCommand('maximizeWindow', { elementId });
|
|
287
|
-
return await this.getWindowRect();
|
|
288
|
-
}
|
|
289
|
-
async function minimizeWindow() {
|
|
290
|
-
const elementId = (await this.sendCommand('saveRootElementToTable', {}))?.trim();
|
|
209
|
+
const elementId = (await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildCommand())).trim();
|
|
291
210
|
if (!elementId) {
|
|
292
211
|
throw new base_driver_1.errors.NoSuchWindowError('No active window found for this session.');
|
|
293
212
|
}
|
|
294
|
-
await this.
|
|
295
|
-
return await this.getWindowRect();
|
|
213
|
+
return await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildGetPropertyCommand(powershell_1.Property.NAME));
|
|
296
214
|
}
|
|
297
215
|
async function setWindowRect(x, y, width, height) {
|
|
298
216
|
if (width !== null && width < 0) {
|
|
@@ -301,253 +219,67 @@ async function setWindowRect(x, y, width, height) {
|
|
|
301
219
|
if (height !== null && height < 0) {
|
|
302
220
|
throw new base_driver_1.errors.InvalidArgumentError('height must be a non-negative integer.');
|
|
303
221
|
}
|
|
304
|
-
const elementId = (await this.
|
|
222
|
+
const elementId = (await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildCommand())).trim();
|
|
305
223
|
if (!elementId) {
|
|
306
224
|
throw new base_driver_1.errors.NoSuchWindowError('No active window found for this session.');
|
|
307
225
|
}
|
|
308
|
-
|
|
309
|
-
// restore to normal state first so CanMove/CanResize are true.
|
|
310
|
-
await this.sendCommand('restoreWindow', { elementId });
|
|
226
|
+
const el = new powershell_1.FoundAutomationElement(elementId);
|
|
311
227
|
if (x !== null && y !== null) {
|
|
312
|
-
await this.
|
|
228
|
+
await this.sendPowerShellCommand(el.buildMoveCommand(x, y));
|
|
313
229
|
}
|
|
314
230
|
if (width !== null && height !== null) {
|
|
315
|
-
await this.
|
|
231
|
+
await this.sendPowerShellCommand(el.buildResizeCommand(width, height));
|
|
316
232
|
}
|
|
317
233
|
return await this.getWindowRect();
|
|
318
234
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
* getChildProcessIds and accumulates them so they survive if the launcher
|
|
324
|
-
* exits before the next poll. This lets stub-launcher apps (e.g. the
|
|
325
|
-
* notepad.exe that immediately re-execs the real one in a child process) be
|
|
326
|
-
* detected without waiting out the full timeout on a windowless PID.
|
|
327
|
-
*
|
|
328
|
-
* Returns the HWND of the found window together with all discovered PIDs so
|
|
329
|
-
* the caller can use them for subsequent ProcessId-based searches without a
|
|
330
|
-
* separate getProcessIds round-trip.
|
|
331
|
-
*/
|
|
332
|
-
async function waitForNewWindow(launcherPid, timeout) {
|
|
235
|
+
async function attachToApplicationWindow(processIds) {
|
|
236
|
+
const trackedPids = new Set(processIds);
|
|
237
|
+
this.log.debug(`Attaching to application window. Process IDs: [${[...trackedPids].join(', ')}]`);
|
|
238
|
+
const timeout = (this.caps['ms:waitForAppLaunch'] ?? 0) * 1000 || SLEEP_INTERVAL_MS * 20;
|
|
333
239
|
const start = Date.now();
|
|
334
240
|
let attempts = 0;
|
|
335
|
-
const knownPids = new Set([launcherPid]);
|
|
336
241
|
while (Date.now() - start < timeout) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
242
|
+
// Discover child processes of all currently-tracked PIDs
|
|
243
|
+
const pidList = [...trackedPids].join(', ');
|
|
244
|
+
const childPidResult = await this.sendPowerShellCommand(
|
|
245
|
+
/* ps1 */ `@(Get-CimInstance Win32_Process | Where-Object { $_.ParentProcessId -in @(${pidList}) }).ProcessId`);
|
|
246
|
+
for (const token of childPidResult.split('\n').map((s) => s.trim()).filter(Boolean)) {
|
|
247
|
+
const childPid = Number(token);
|
|
248
|
+
if (!isNaN(childPid) && childPid > 0 && !trackedPids.has(childPid)) {
|
|
249
|
+
this.log.debug(`Discovered child process PID ${childPid} spawned by tracked PIDs`);
|
|
250
|
+
trackedPids.add(childPid);
|
|
341
251
|
}
|
|
342
252
|
}
|
|
343
|
-
const
|
|
253
|
+
const currentPids = [...trackedPids];
|
|
254
|
+
const handles = (0, user32_1.getWindowAllHandlesForProcessIds)(currentPids);
|
|
344
255
|
if (handles.length > 0) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
catch {
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
if (!candidateId) {
|
|
371
|
-
continue;
|
|
372
|
-
}
|
|
373
|
-
if (!fallbackElementId) {
|
|
374
|
-
fallbackElementId = candidateId;
|
|
375
|
-
}
|
|
376
|
-
const focusables = await this.sendCommand('findElements', {
|
|
377
|
-
scope: 'descendants',
|
|
378
|
-
condition: (0, conditions_1.propertyCondition)('IsKeyboardFocusable', true),
|
|
379
|
-
contextElementId: candidateId,
|
|
380
|
-
});
|
|
381
|
-
if (focusables && focusables.length >= 2) {
|
|
382
|
-
this.log.info(`Attaching to window 0x${hwnd.toString(16).padStart(8, '0')} (${focusables.length} focusable descendants).`);
|
|
383
|
-
await this.sendCommand('setRootElementFromElementId', { elementId: candidateId });
|
|
384
|
-
const isNotNull = await this.sendCommand('checkRootElementNotNull', {});
|
|
385
|
-
if (isNotNull) {
|
|
386
|
-
const rootId = await this.sendCommand('saveRootElementToTable', {});
|
|
387
|
-
const nwh = Number(await this.sendCommand('getProperty', { elementId: rootId, property: 'NativeWindowHandle' }));
|
|
388
|
-
(0, user32_1.trySetForegroundWindow)(nwh);
|
|
389
|
-
}
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Attaches the driver session to the window spawned by launcherPid.
|
|
397
|
-
*
|
|
398
|
-
* Delegates process-tree discovery to waitForNewWindow, then resolves the
|
|
399
|
-
* Win32 HWND to a UIA element via elementFromHandle (with a short ProcessId
|
|
400
|
-
* fallback for the rare case where UIA registration lags the Win32 window).
|
|
401
|
-
* Returns focused=false when the attached window looks like a splash screen
|
|
402
|
-
* (fewer than 2 keyboard-focusable descendants), signalling the caller to
|
|
403
|
-
* run waitForMainWindow.
|
|
404
|
-
*/
|
|
405
|
-
async function attachToApplicationWindow(launcherPid, options = {}) {
|
|
406
|
-
const { deadline = null } = options;
|
|
407
|
-
const waitForAppLaunchMs = normalizeWaitForAppLaunchMs(this.caps['ms:waitForAppLaunch']) || POLL_INTERVAL_MS * MAX_POLL_ATTEMPTS;
|
|
408
|
-
const windowTimeout = deadline
|
|
409
|
-
? Math.min(waitForAppLaunchMs, Math.max(0, deadline - Date.now()))
|
|
410
|
-
: waitForAppLaunchMs;
|
|
411
|
-
const { handle: nativeWindowHandle, knownPids } = await waitForNewWindow.call(this, launcherPid, windowTimeout);
|
|
412
|
-
let elementId = '';
|
|
413
|
-
// Short retry for UIA registration lag: the Win32 window exists (waitForNewWindow
|
|
414
|
-
// confirmed it), but the UIA COM provider may take a moment to initialise.
|
|
415
|
-
// 5 attempts × 200 ms = 1 s — enough for any real app; avoids the old 6 s burn.
|
|
416
|
-
for (let i = 1; i <= 5; i++) {
|
|
417
|
-
try {
|
|
418
|
-
elementId = await this.sendCommand('elementFromHandle', { handle: nativeWindowHandle }) ?? '';
|
|
419
|
-
}
|
|
420
|
-
catch {
|
|
421
|
-
elementId = '';
|
|
422
|
-
}
|
|
423
|
-
if (!elementId) {
|
|
424
|
-
for (const pid of knownPids) {
|
|
425
|
-
const foundId = await this.sendCommand('findElement', {
|
|
426
|
-
scope: 'children',
|
|
427
|
-
condition: (0, conditions_1.propertyCondition)('ProcessId', pid),
|
|
428
|
-
contextElementId: null,
|
|
429
|
-
}) ?? '';
|
|
430
|
-
if (foundId) {
|
|
431
|
-
this.log.info(`Found window by ProcessId ${pid} (handle mismatch: Win32=0x${nativeWindowHandle.toString(16).padStart(8, '0')})`);
|
|
432
|
-
elementId = foundId;
|
|
433
|
-
break;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
if (elementId) {
|
|
438
|
-
break;
|
|
439
|
-
}
|
|
440
|
-
this.log.debug(`Window 0x${nativeWindowHandle.toString(16).padStart(8, '0')} not yet in UIA tree, retry ${i}/5...`);
|
|
441
|
-
await (0, util_1.sleep)(POLL_INTERVAL_MS);
|
|
442
|
-
}
|
|
443
|
-
if (!elementId) {
|
|
444
|
-
throw new base_driver_1.errors.UnknownError(`Window 0x${nativeWindowHandle.toString(16).padStart(8, '0')} did not appear in the UI Automation tree.`);
|
|
445
|
-
}
|
|
446
|
-
await this.sendCommand('setRootElementFromElementId', { elementId });
|
|
447
|
-
const isNotNull = await this.sendCommand('checkRootElementNotNull', {});
|
|
448
|
-
if (isNotNull) {
|
|
449
|
-
const rootId = await this.sendCommand('saveRootElementToTable', {});
|
|
450
|
-
const nwh = Number(await this.sendCommand('getProperty', { elementId: rootId, property: 'NativeWindowHandle' }));
|
|
451
|
-
let focused = (0, user32_1.trySetForegroundWindow)(nwh);
|
|
452
|
-
if (!focused) {
|
|
453
|
-
try {
|
|
454
|
-
await this.focusElement({ [base_driver_1.W3C_ELEMENT_KEY]: elementId });
|
|
455
|
-
focused = true;
|
|
456
|
-
}
|
|
457
|
-
catch {
|
|
458
|
-
// Window cannot receive focus (e.g. splash screen)
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
// Splash-screen guard: probe for focusable descendants.
|
|
462
|
-
// Splash screens have 0–1; real app windows have several.
|
|
463
|
-
if (focused) {
|
|
464
|
-
try {
|
|
465
|
-
const focusables = await this.sendCommand('findElements', {
|
|
466
|
-
scope: 'descendants',
|
|
467
|
-
condition: (0, conditions_1.propertyCondition)('IsKeyboardFocusable', true),
|
|
468
|
-
contextElementId: elementId,
|
|
469
|
-
});
|
|
470
|
-
if (!focusables || focusables.length < 2) {
|
|
471
|
-
this.log.info(`Attached window has only ${focusables?.length ?? 0} focusable descendant(s); treating as a splash screen.`);
|
|
472
|
-
focused = false;
|
|
256
|
+
this.log.debug(`Found ${handles.length} window handle(s) for PIDs [${currentPids.join(', ')}]: ${handles.map((h) => `0x${h.toString(16).padStart(8, '0')}`).join(', ')}`);
|
|
257
|
+
for (const handle of handles) {
|
|
258
|
+
const elementId = await this.sendPowerShellCommand(powershell_1.AutomationElement.rootElement.findFirst(powershell_1.TreeScope.CHILDREN, new powershell_1.PropertyCondition(powershell_1.Property.NATIVE_WINDOW_HANDLE, new powershell_1.PSInt32(handle))).buildCommand());
|
|
259
|
+
if (elementId.trim()) {
|
|
260
|
+
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new powershell_1.FoundAutomationElement(elementId).buildCommand()}`);
|
|
261
|
+
if ((await this.sendPowerShellCommand(/* ps1 */ `$null -ne $rootElement`)).toLowerCase() === 'true') {
|
|
262
|
+
const confirmedHandle = Number(await this.sendPowerShellCommand(powershell_1.AutomationElement.automationRoot.buildGetPropertyCommand(powershell_1.Property.NATIVE_WINDOW_HANDLE)));
|
|
263
|
+
this.log.info(`Successfully attached to window. Native window handle: 0x${confirmedHandle.toString(16).padStart(8, '0')}`);
|
|
264
|
+
this.appProcessIds = currentPids;
|
|
265
|
+
if (!(0, user32_1.trySetForegroundWindow)(confirmedHandle)) {
|
|
266
|
+
try {
|
|
267
|
+
await this.focusElement({
|
|
268
|
+
[base_driver_1.W3C_ELEMENT_KEY]: elementId,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
this.log.warn(`Could not focus app window: ${e.message}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
473
277
|
}
|
|
474
278
|
}
|
|
475
|
-
catch {
|
|
476
|
-
// leave focused as-is
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return { focused, knownPids };
|
|
480
|
-
}
|
|
481
|
-
return { focused: false, knownPids };
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Waits for a splash screen to disappear and re-attaches to the app's main window.
|
|
485
|
-
*
|
|
486
|
-
* After `attachToApplicationWindow` attaches to a window that can't receive focus
|
|
487
|
-
* (a splash screen), this function polls until the current root element becomes
|
|
488
|
-
* stale (splash screen closed) or a new, different window handle appears for the
|
|
489
|
-
* same process. It then re-attaches to the new main window.
|
|
490
|
-
*/
|
|
491
|
-
async function waitForMainWindow(knownPids, deadline) {
|
|
492
|
-
// Capture the splash screen's window handle so we can detect when a new window appears
|
|
493
|
-
let splashHandle = null;
|
|
494
|
-
try {
|
|
495
|
-
const rootId = await this.sendCommand('saveRootElementToTable', {});
|
|
496
|
-
splashHandle = Number(await this.sendCommand('getProperty', { elementId: rootId, property: 'NativeWindowHandle' }));
|
|
497
|
-
}
|
|
498
|
-
catch {
|
|
499
|
-
// If we can't get the handle, we'll just watch for staleness
|
|
500
|
-
}
|
|
501
|
-
this.log.debug(`Splash screen handle: ${splashHandle != null ? `0x${splashHandle.toString(16).padStart(8, '0')}` : 'unknown'}. Polling for main window...`);
|
|
502
|
-
let attempt = 0;
|
|
503
|
-
while (Date.now() < deadline) {
|
|
504
|
-
attempt++;
|
|
505
|
-
await (0, util_1.sleep)(POLL_INTERVAL_MS);
|
|
506
|
-
// Check if the current root element is still alive
|
|
507
|
-
let rootStale = false;
|
|
508
|
-
try {
|
|
509
|
-
const isNotNull = await this.sendCommand('checkRootElementNotNull', {});
|
|
510
|
-
if (!isNotNull) {
|
|
511
|
-
rootStale = true;
|
|
512
|
-
}
|
|
513
|
-
else {
|
|
514
|
-
// Try to read a property — if the element is stale, this throws
|
|
515
|
-
const rootId = await this.sendCommand('saveRootElementToTable', {});
|
|
516
|
-
await this.sendCommand('getProperty', { elementId: rootId, property: 'Name' });
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
catch {
|
|
520
|
-
rootStale = true;
|
|
521
|
-
}
|
|
522
|
-
if (rootStale) {
|
|
523
|
-
this.log.info(`Splash screen closed (root element stale after ${attempt} polls). Re-attaching to main window via attachToApplicationWindow...`);
|
|
524
|
-
// Clear the stale root so attachToApplicationWindow starts fresh
|
|
525
|
-
await this.sendCommand('setRootElementNull', {});
|
|
526
|
-
// attachToApplicationWindow has its own polling loop for the UIA tree,
|
|
527
|
-
// so it will wait until the new main window registers.
|
|
528
|
-
const result = await this.attachToApplicationWindow(knownPids[0], { deadline });
|
|
529
|
-
if (result.focused) {
|
|
530
|
-
this.log.info('Successfully attached to the main application window after splash screen.');
|
|
531
|
-
}
|
|
532
|
-
else {
|
|
533
|
-
this.log.info('Attached to main window (could not focus, but window found).');
|
|
534
|
-
}
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
// Root is still alive — check if the process now has a different (new) window
|
|
538
|
-
if (splashHandle != null) {
|
|
539
|
-
const handles = (0, user32_1.getWindowAllHandlesForProcessIds)(knownPids);
|
|
540
|
-
const newHandle = handles.find((h) => h !== splashHandle);
|
|
541
|
-
if (newHandle) {
|
|
542
|
-
this.log.info(`New window detected (0x${newHandle.toString(16).padStart(8, '0')}) alongside splash screen (attempt ${attempt}). Waiting for splash to close...`);
|
|
543
|
-
// Don't re-attach yet — let the splash finish naturally.
|
|
544
|
-
// Once it closes, the root-stale path above will handle re-attach.
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
if (attempt % 10 === 0) {
|
|
548
|
-
this.log.debug(`Still waiting for splash screen to close... (attempt ${attempt})`);
|
|
549
279
|
}
|
|
280
|
+
this.log.debug(`No attachable window found yet. Sleeping for ${SLEEP_INTERVAL_MS} milliseconds and retrying... (${++attempts}/${Math.floor(timeout / SLEEP_INTERVAL_MS)})`);
|
|
281
|
+
await (0, util_1.sleep)(SLEEP_INTERVAL_MS);
|
|
550
282
|
}
|
|
551
|
-
|
|
283
|
+
throw new Error('Timed out waiting to attach to application window.');
|
|
552
284
|
}
|
|
553
285
|
//# sourceMappingURL=app.js.map
|