Skip to content

Commit cdca4a7

Browse files
authored
Merge pull request #62 from AutomateThePlanet/fix/classic-app-startup-fix
fix: fixed not being able to attach to slow-starting classic apps
2 parents 560eb87 + f25b000 commit cdca4a7

File tree

1 file changed

+45
-28
lines changed

1 file changed

+45
-28
lines changed

lib/commands/app.ts

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ const GET_SCREENSHOT_COMMAND = pwsh /* ps1 */ `
5858
[Convert]::ToBase64String($stream.ToArray())
5959
`;
6060

61+
const SLEEP_INTERVAL_MS = 500;
62+
6163
export async function getPageSource(this: NovaWindowsDriver): Promise<string> {
6264
return await this.sendPowerShellCommand(GET_PAGE_SOURCE_COMMAND.format(AutomationElement.automationRoot));
6365
}
@@ -126,8 +128,8 @@ export async function setWindow(this: NovaWindowsDriver, nameOrHandle: string):
126128
return;
127129
}
128130

129-
this.log.info(`Failed to locate window with name '${name}'. Sleeping for 500 milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
130-
await sleep(500); // TODO: make a setting for the sleep timeout
131+
this.log.info(`Failed to locate window with name '${name}'. Sleeping for ${SLEEP_INTERVAL_MS} milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
132+
await sleep(SLEEP_INTERVAL_MS); // TODO: make a setting for the sleep timeout
131133
}
132134

133135
throw new errors.NoSuchWindowError(`No window was found with name or handle '${nameOrHandle}'.`);
@@ -172,7 +174,7 @@ export async function changeRootElement(this: NovaWindowsDriver, pathOrNativeWin
172174
if (path.includes('!') && path.includes('_') && !(path.includes('/') || path.includes('\\'))) {
173175
this.log.debug('Detected app path to be in the UWP format.');
174176
await this.sendPowerShellCommand(/* ps1 */ `Start-Process 'explorer.exe' 'shell:AppsFolder\\${path}'${this.caps.appArguments ? ` -ArgumentList '${this.caps.appArguments}'` : ''}`);
175-
await sleep((this.caps['ms:waitForAppLaunch'] ?? 0) * 1000 || 500);
177+
await sleep((this.caps['ms:waitForAppLaunch'] ?? 0) * 1000 || SLEEP_INTERVAL_MS);
176178
for (let i = 1; i <= 20; i++) {
177179
const result = await this.sendPowerShellCommand(/* ps1 */ `(Get-Process -Name 'ApplicationFrameHost').Id`);
178180
const processIds = result.split('\n').map((pid) => pid.trim()).filter(Boolean).map(Number);
@@ -185,8 +187,8 @@ export async function changeRootElement(this: NovaWindowsDriver, pathOrNativeWin
185187
// noop
186188
}
187189

188-
this.log.info(`Failed to locate window of the app. Sleeping for 500 milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
189-
await sleep(500); // TODO: make a setting for the sleep timeout
190+
this.log.info(`Failed to locate window of the app. Sleeping for ${SLEEP_INTERVAL_MS} milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
191+
await sleep(SLEEP_INTERVAL_MS); // TODO: make a setting for the sleep timeout
190192
}
191193
} else {
192194
this.log.debug('Detected app path to be in the classic format.');
@@ -210,8 +212,8 @@ export async function changeRootElement(this: NovaWindowsDriver, pathOrNativeWin
210212
}
211213
}
212214

213-
this.log.info(`Failed to locate window of the app. Sleeping for 500 milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
214-
await sleep(500); // TODO: make a setting for the sleep timeout
215+
this.log.info(`Failed to locate window of the app. Sleeping for ${SLEEP_INTERVAL_MS} milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
216+
await sleep(SLEEP_INTERVAL_MS); // TODO: make a setting for the sleep timeout
215217
}
216218
}
217219

@@ -280,30 +282,45 @@ export async function setWindowRect(
280282
return await this.getWindowRect();
281283
}
282284

283-
export async function attachToApplicationWindow(this: NovaWindowsDriver, processIds: number[]): Promise<void> {
284-
const nativeWindowHandles = getWindowAllHandlesForProcessIds(processIds);
285-
this.log.debug(`Detected the following native window handles for the given process IDs: ${nativeWindowHandles.map((handle) => `0x${handle.toString(16).padStart(8, '0')}`).join(', ')}`);
285+
export async function waitForNewWindow(this: NovaWindowsDriver, pid: number, timeout: number): Promise<number> {
286+
const start = Date.now();
287+
let attempts = 0;
286288

287-
if (nativeWindowHandles.length !== 0) {
288-
let elementId = '';
289-
for (let i = 1; i <= 20; i++) {
290-
elementId = await this.sendPowerShellCommand(AutomationElement.rootElement.findFirst(TreeScope.CHILDREN, new PropertyCondition(Property.NATIVE_WINDOW_HANDLE, new PSInt32(nativeWindowHandles[0]))).buildCommand());
291-
if (elementId) {
292-
break;
293-
}
294-
this.log.info(`The window with handle 0x${nativeWindowHandles[0].toString(16).padStart(8, '0')} is not yet available in the UI Automation tree. Sleeping for 500 milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
295-
await sleep(500); // TODO: make a setting for the sleep timeout
289+
while (Date.now() - start < timeout) {
290+
const handles = getWindowAllHandlesForProcessIds([pid]);
291+
292+
if (handles.length > 0) {
293+
return handles[handles.length - 1];
296294
}
297295

298-
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new FoundAutomationElement(elementId).buildCommand()}`);
299-
if ((await this.sendPowerShellCommand(/* ps1 */ `$null -ne $rootElement`)).toLowerCase() === 'true') {
300-
const nativeWindowHandle = Number(await this.sendPowerShellCommand(AutomationElement.automationRoot.buildGetPropertyCommand(Property.NATIVE_WINDOW_HANDLE)));
301-
if (!trySetForegroundWindow(nativeWindowHandle)) {
302-
await this.focusElement({
303-
[W3C_ELEMENT_KEY]: elementId,
304-
} satisfies Element);
305-
};
306-
return;
296+
this.log.debug(`Waiting for the process window to appear... (${++attempts}/${Math.floor(timeout / SLEEP_INTERVAL_MS)})`);
297+
await sleep(SLEEP_INTERVAL_MS);
298+
}
299+
300+
throw new Error('Timed out waiting for window.');
301+
}
302+
303+
export async function attachToApplicationWindow(this: NovaWindowsDriver, processIds: number[]): Promise<void> {
304+
const nativeWindowHandle = await waitForNewWindow.call(this, processIds[0], this.caps['ms:waitForAppLaunch'] ?? SLEEP_INTERVAL_MS * 20);
305+
306+
let elementId = '';
307+
for (let i = 1; i <= 20; i++) {
308+
elementId = await this.sendPowerShellCommand(AutomationElement.rootElement.findFirst(TreeScope.CHILDREN, new PropertyCondition(Property.NATIVE_WINDOW_HANDLE, new PSInt32(nativeWindowHandle))).buildCommand());
309+
if (elementId) {
310+
break;
307311
}
312+
this.log.info(`The window with handle 0x${nativeWindowHandle.toString(16).padStart(8, '0')} is not yet available in the UI Automation tree. Sleeping for ${SLEEP_INTERVAL_MS} milliseconds and retrying... (${i}/20)`); // TODO: make a setting for the number of retries or timeout
313+
await sleep(SLEEP_INTERVAL_MS); // TODO: make a setting for the sleep timeout
314+
}
315+
316+
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new FoundAutomationElement(elementId).buildCommand()}`);
317+
if ((await this.sendPowerShellCommand(/* ps1 */ `$null -ne $rootElement`)).toLowerCase() === 'true') {
318+
const nativeWindowHandle = Number(await this.sendPowerShellCommand(AutomationElement.automationRoot.buildGetPropertyCommand(Property.NATIVE_WINDOW_HANDLE)));
319+
if (!trySetForegroundWindow(nativeWindowHandle)) {
320+
await this.focusElement({
321+
[W3C_ELEMENT_KEY]: elementId,
322+
} satisfies Element);
323+
};
324+
return;
308325
}
309326
}

0 commit comments

Comments
 (0)