Skip to content

Commit 117a77e

Browse files
committed
Merge branch 'main' of github.com:Yuri-byte/appium-novawindows-driver into fix/windows-10-scaling
2 parents 61fc837 + 4e37498 commit 117a77e

File tree

5 files changed

+65
-40
lines changed

5 files changed

+65
-40
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## [1.3.1](https://github.com/AutomateThePlanet/appium-novawindows-driver/compare/v1.3.0...v1.3.1) (2026-03-09)
2+
3+
### Bug Fixes
4+
5+
* add stderr encoding for PowerShell session ([a233063](https://github.com/AutomateThePlanet/appium-novawindows-driver/commit/a233063b4509e41c047fcc3603b29b944c5ac374))
6+
* fixed incorrect $pattern variable reference ([a0afceb](https://github.com/AutomateThePlanet/appium-novawindows-driver/commit/a0afceb7e682219b5e82759c52e20c59bff1225f))
7+
* fixed not being able to attach to slow-starting classic apps on session creation ([f25b000](https://github.com/AutomateThePlanet/appium-novawindows-driver/commit/f25b000533be1ef2a7c0bc350bf62a3cd1b60a45))
8+
19
## [1.3.0](https://github.com/AutomateThePlanet/appium-novawindows-driver/compare/v1.2.0...v1.3.0) (2026-03-06)
210

311
### Features

lib/commands/app.ts

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

71+
const SLEEP_INTERVAL_MS = 500;
72+
7173
export async function getPageSource(this: NovaWindowsDriver): Promise<string> {
7274
return await this.sendPowerShellCommand(GET_PAGE_SOURCE_COMMAND.format(AutomationElement.automationRoot));
7375
}
@@ -136,8 +138,8 @@ export async function setWindow(this: NovaWindowsDriver, nameOrHandle: string):
136138
return;
137139
}
138140

139-
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
140-
await sleep(500); // TODO: make a setting for the sleep timeout
141+
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
142+
await sleep(SLEEP_INTERVAL_MS); // TODO: make a setting for the sleep timeout
141143
}
142144

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

198-
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
199-
await sleep(500); // TODO: make a setting for the sleep timeout
200+
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
201+
await sleep(SLEEP_INTERVAL_MS); // TODO: make a setting for the sleep timeout
200202
}
201203
} else {
202204
this.log.debug('Detected app path to be in the classic format.');
@@ -220,8 +222,8 @@ export async function changeRootElement(this: NovaWindowsDriver, pathOrNativeWin
220222
}
221223
}
222224

223-
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
224-
await sleep(500); // TODO: make a setting for the sleep timeout
225+
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
226+
await sleep(SLEEP_INTERVAL_MS); // TODO: make a setting for the sleep timeout
225227
}
226228
}
227229

@@ -290,30 +292,45 @@ export async function setWindowRect(
290292
return await this.getWindowRect();
291293
}
292294

293-
export async function attachToApplicationWindow(this: NovaWindowsDriver, processIds: number[]): Promise<void> {
294-
const nativeWindowHandles = getWindowAllHandlesForProcessIds(processIds);
295-
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(', ')}`);
295+
export async function waitForNewWindow(this: NovaWindowsDriver, pid: number, timeout: number): Promise<number> {
296+
const start = Date.now();
297+
let attempts = 0;
296298

297-
if (nativeWindowHandles.length !== 0) {
298-
let elementId = '';
299-
for (let i = 1; i <= 20; i++) {
300-
elementId = await this.sendPowerShellCommand(AutomationElement.rootElement.findFirst(TreeScope.CHILDREN, new PropertyCondition(Property.NATIVE_WINDOW_HANDLE, new PSInt32(nativeWindowHandles[0]))).buildCommand());
301-
if (elementId) {
302-
break;
303-
}
304-
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
305-
await sleep(500); // TODO: make a setting for the sleep timeout
299+
while (Date.now() - start < timeout) {
300+
const handles = getWindowAllHandlesForProcessIds([pid]);
301+
302+
if (handles.length > 0) {
303+
return handles[handles.length - 1];
306304
}
307305

308-
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new FoundAutomationElement(elementId).buildCommand()}`);
309-
if ((await this.sendPowerShellCommand(/* ps1 */ `$null -ne $rootElement`)).toLowerCase() === 'true') {
310-
const nativeWindowHandle = Number(await this.sendPowerShellCommand(AutomationElement.automationRoot.buildGetPropertyCommand(Property.NATIVE_WINDOW_HANDLE)));
311-
if (!trySetForegroundWindow(nativeWindowHandle)) {
312-
await this.focusElement({
313-
[W3C_ELEMENT_KEY]: elementId,
314-
} satisfies Element);
315-
};
316-
return;
306+
this.log.debug(`Waiting for the process window to appear... (${++attempts}/${Math.floor(timeout / SLEEP_INTERVAL_MS)})`);
307+
await sleep(SLEEP_INTERVAL_MS);
308+
}
309+
310+
throw new Error('Timed out waiting for window.');
311+
}
312+
313+
export async function attachToApplicationWindow(this: NovaWindowsDriver, processIds: number[]): Promise<void> {
314+
const nativeWindowHandle = await waitForNewWindow.call(this, processIds[0], this.caps['ms:waitForAppLaunch'] ?? SLEEP_INTERVAL_MS * 20);
315+
316+
let elementId = '';
317+
for (let i = 1; i <= 20; i++) {
318+
elementId = await this.sendPowerShellCommand(AutomationElement.rootElement.findFirst(TreeScope.CHILDREN, new PropertyCondition(Property.NATIVE_WINDOW_HANDLE, new PSInt32(nativeWindowHandle))).buildCommand());
319+
if (elementId) {
320+
break;
317321
}
322+
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
323+
await sleep(SLEEP_INTERVAL_MS); // TODO: make a setting for the sleep timeout
324+
}
325+
326+
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = ${new FoundAutomationElement(elementId).buildCommand()}`);
327+
if ((await this.sendPowerShellCommand(/* ps1 */ `$null -ne $rootElement`)).toLowerCase() === 'true') {
328+
const nativeWindowHandle = Number(await this.sendPowerShellCommand(AutomationElement.automationRoot.buildGetPropertyCommand(Property.NATIVE_WINDOW_HANDLE)));
329+
if (!trySetForegroundWindow(nativeWindowHandle)) {
330+
await this.focusElement({
331+
[W3C_ELEMENT_KEY]: elementId,
332+
} satisfies Element);
333+
};
334+
return;
318335
}
319336
}

lib/commands/functions.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,18 @@ export const PAGE_SOURCE = pwsh /* ps1 */ `
152152
$pattern = $null
153153
154154
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)
155+
$newXmlElement.SetAttribute("CanMaximize", $pattern.Current.CanMaximize)
156+
$newXmlElement.SetAttribute("CanMinimize", $pattern.Current.CanMinimize)
157+
$newXmlElement.SetAttribute("IsModal", $pattern.Current.IsModal)
158+
$newXmlElement.SetAttribute("WindowVisualState", $pattern.Current.WindowVisualState)
159+
$newXmlElement.SetAttribute("WindowInteractionState", $pattern.Current.WindowInteractionState)
160+
$newXmlElement.SetAttribute("IsTopmost", $pattern.Current.IsTopmost)
161161
}
162162
163163
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)
164+
$newXmlElement.SetAttribute("CanRotate", $pattern.Current.CanRotate)
165+
$newXmlElement.SetAttribute("CanResize", $pattern.Current.CanResize)
166+
$newXmlElement.SetAttribute("CanMove", $pattern.Current.CanMove)
167167
}
168168
169169
# TODO: more to be added depending on the available patterns
@@ -189,4 +189,4 @@ export const PAGE_SOURCE = pwsh /* ps1 */ `
189189
190190
return $xmlElement
191191
}
192-
`;
192+
`;

lib/commands/powershell.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const INIT_ELEMENT_TABLE = /* ps1 */ `$elementTable = New-Object System.Collecti
1414
export async function startPowerShellSession(this: NovaWindowsDriver): Promise<void> {
1515
const powerShell = spawn('powershell.exe', ['-NoExit', '-Command', '-']);
1616
powerShell.stdout.setEncoding('utf8');
17-
powerShell.stdout.setEncoding('utf8');
17+
powerShell.stderr.setEncoding('utf8');
1818

1919
powerShell.stdout.on('data', (chunk: any) => {
2020
this.powerShellStdOut += chunk.toString();

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "appium-novawindows-driver",
3-
"version": "1.3.0",
3+
"version": "1.3.1",
44
"description": "Appium driver for Windows",
55
"keywords": [
66
"appium",

0 commit comments

Comments
 (0)