Skip to content

Commit fa52a19

Browse files
authored
Merge pull request #57 from Yuri-byte/feat/unit-testing-commands [skip ci]
Feat/unit testing commands
2 parents 6dc2125 + 4662035 commit fa52a19

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+5106
-70
lines changed

README.md

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -516,31 +516,78 @@ Position | Type | Description | Example
516516

517517
### windows: startRecordingScreen
518518

519-
To be implemented.
519+
Starts screen recording using the **bundled ffmpeg** included with the driver. There is no system PATH fallback: if the bundle is not present (e.g. driver was not installed via npm with dependencies), screen recording is not available and the driver reports a clear error.
520520

521521
### windows: stopRecordingScreen
522522

523-
To be implemented.
523+
Stops the current screen recording and returns the video (base64 or uploads to a remote path if specified).
524524

525525
### windows: deleteFile
526526

527-
To be implemented.
527+
Deletes a file on the Windows machine. Uses PowerShell `Remove-Item -Path ... -Force`. Paths containing `[`, `]`, or `?` use `-LiteralPath` for correct interpretation.
528+
529+
#### Arguments
530+
531+
Name | Type | Required | Description | Example
532+
--- | --- | --- | --- | ---
533+
path | string | yes | Absolute or relative path to the file to delete. | `C:\Temp\file.txt`
528534

529535
### windows: deleteFolder
530536

531-
To be implemented.
537+
Deletes a folder on the Windows machine. Uses PowerShell `Remove-Item -Path ... -Force` with optional `-Recurse`. Paths containing `[`, `]`, or `?` use `-LiteralPath`.
538+
539+
#### Arguments
540+
541+
Name | Type | Required | Description | Example
542+
--- | --- | --- | --- | ---
543+
path | string | yes | Absolute or relative path to the folder to delete. | `C:\Temp\MyFolder`
544+
recursive | boolean | no | If true (default), delete contents recursively. If false, only remove the folder when empty. | `true`
532545

533546
### windows: launchApp
534547

535-
To be implemented.
548+
Re-launches the application configured in the `app` session capability. The app path or App User Model ID (AUMID) must have been set when the session was created. Typically used to reopen an app after it has been closed with `windows: closeApp`.
549+
550+
This command takes no arguments.
551+
552+
#### Example
553+
554+
```javascript
555+
// Re-launch the app set in the session capability
556+
await driver.executeScript('windows: launchApp', []);
557+
```
536558

537559
### windows: closeApp
538560

539-
To be implemented.
561+
Closes the current root application window by sending a close command via the Windows UI Automation WindowPattern. Clears the root element reference in the session afterward. Throws a `NoSuchWindowError` if no active window is found.
562+
563+
This command takes no arguments.
564+
565+
#### Example
566+
567+
```javascript
568+
// Close the current app window
569+
await driver.executeScript('windows: closeApp', []);
570+
```
540571

541572
### windows: clickAndDrag
542573

543-
To be implemented.
574+
Performs a click-and-drag: move to the start position, press the mouse button, move to the end position over the given duration, then release. Start and end can be specified by element (center or offset) or by screen coordinates. Uses the same Windows input APIs as other pointer actions.
575+
576+
#### Arguments
577+
578+
Name | Type | Required | Description | Example
579+
--- | --- | --- | --- | ---
580+
startElementId | string | no* | Element ID for drag start. Use *or* startX/startY. | `1.2.3.4.5`
581+
startX | number | no* | X coordinate for drag start (with startY). | `100`
582+
startY | number | no* | Y coordinate for drag start (with startX). | `200`
583+
endElementId | string | no* | Element ID for drag end. Use *or* endX/endY. | `1.2.3.4.6`
584+
endX | number | no* | X coordinate for drag end (with endY). | `300`
585+
endY | number | no* | Y coordinate for drag end (with endX). | `400`
586+
modifierKeys | string or string[] | no | Keys to hold during drag: `shift`, `ctrl`, `alt`, `win`. | `["ctrl"]`
587+
durationMs | number | no | Duration of the move from start to end (default: 500). | `300`
588+
button | string | no | Mouse button: `left` (default), `middle`, `right`, `back`, `forward`. | `left`
589+
590+
\* Provide either startElementId or both startX and startY; and either endElementId or both endX and endY.
544591

545592
## Development
546593

eslint.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ import appiumConfig from '@appium/eslint-config-appium-ts';
88
export default defineConfig(
99
eslint.configs.recommended,
1010
...appiumConfig,
11+
{
12+
files: ['test/e2e/**/*.ts'],
13+
},
1114
);

lib/commands/actions.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,33 @@ export async function handleKeyAction(this: NovaWindowsDriver, action: KeyAction
227227
}
228228
}
229229
}
230+
231+
export async function releaseActions(this: NovaWindowsDriver): Promise<void> {
232+
if (this.keyboardState.shift) {
233+
keyUp(Key.SHIFT);
234+
keyUp(Key.R_SHIFT);
235+
this.keyboardState.shift = false;
236+
}
237+
if (this.keyboardState.ctrl) {
238+
keyUp(Key.CONTROL);
239+
keyUp(Key.R_CONTROL);
240+
this.keyboardState.ctrl = false;
241+
}
242+
if (this.keyboardState.meta) {
243+
keyUp(Key.META);
244+
keyUp(Key.R_META);
245+
this.keyboardState.meta = false;
246+
}
247+
if (this.keyboardState.alt) {
248+
keyUp(Key.ALT);
249+
keyUp(Key.R_ALT);
250+
this.keyboardState.alt = false;
251+
}
252+
for (const key of this.keyboardState.pressed) {
253+
keyUp(key);
254+
}
255+
this.keyboardState.pressed.clear();
256+
mouseUp(0);
257+
mouseUp(1);
258+
mouseUp(2);
259+
}

lib/commands/app.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,23 @@ export async function setWindow(this: NovaWindowsDriver, nameOrHandle: string):
130130
throw new errors.NoSuchWindowError(`No window was found with name or handle '${nameOrHandle}'.`);
131131
}
132132

133+
export async function closeApp(this: NovaWindowsDriver): Promise<void> {
134+
const result = await this.sendPowerShellCommand(AutomationElement.automationRoot.buildCommand());
135+
const elementId = result.split('\n').map((id) => id.trim()).filter(Boolean)[0];
136+
if (!elementId) {
137+
throw new errors.NoSuchWindowError('No active app window is found for this session.');
138+
}
139+
await this.sendPowerShellCommand(new FoundAutomationElement(elementId).buildCloseCommand());
140+
await this.sendPowerShellCommand(/* ps1 */ `$rootElement = $null`);
141+
}
142+
143+
export async function launchApp(this: NovaWindowsDriver): Promise<void> {
144+
if (!this.caps.app || ['root', 'none'].includes(this.caps.app.toLowerCase())) {
145+
throw new errors.InvalidArgumentError('No app capability is set for this session.');
146+
}
147+
await this.changeRootElement(this.caps.app);
148+
}
149+
133150
export async function changeRootElement(this: NovaWindowsDriver, path: string): Promise<void>
134151
export async function changeRootElement(this: NovaWindowsDriver, nativeWindowHandle: number): Promise<void>
135152
export async function changeRootElement(this: NovaWindowsDriver, pathOrNativeWindowHandle: string | number): Promise<void> {
@@ -152,7 +169,7 @@ export async function changeRootElement(this: NovaWindowsDriver, pathOrNativeWin
152169
if (path.includes('!') && path.includes('_') && !(path.includes('/') || path.includes('\\'))) {
153170
this.log.debug('Detected app path to be in the UWP format.');
154171
await this.sendPowerShellCommand(/* ps1 */ `Start-Process 'explorer.exe' 'shell:AppsFolder\\${path}'${this.caps.appArguments ? ` -ArgumentList '${this.caps.appArguments}'` : ''}`);
155-
await sleep(500); // TODO: make a setting for the initial wait time
172+
await sleep((this.caps['ms:waitForAppLaunch'] ?? 0) * 1000 || 500);
156173
for (let i = 1; i <= 20; i++) {
157174
const result = await this.sendPowerShellCommand(/* ps1 */ `(Get-Process -Name 'ApplicationFrameHost').Id`);
158175
const processIds = result.split('\n').map((pid) => pid.trim()).filter(Boolean).map(Number);
@@ -172,7 +189,7 @@ export async function changeRootElement(this: NovaWindowsDriver, pathOrNativeWin
172189
this.log.debug('Detected app path to be in the classic format.');
173190
const normalizedPath = normalize(path);
174191
await this.sendPowerShellCommand(/* ps1 */ `Start-Process '${normalizedPath}'${this.caps.appArguments ? ` -ArgumentList '${this.caps.appArguments}'` : ''}`);
175-
await sleep(500); // TODO: make a setting for the initial wait time
192+
await sleep((this.caps['ms:waitForAppLaunch'] ?? 0) * 1000 || 500);
176193
for (let i = 1; i <= 20; i++) {
177194
try {
178195
const breadcrumbs = normalizedPath.toLowerCase().split('\\').flatMap((x) => x.split('/'));

lib/commands/device.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { PSString, pwsh$ } from '../powershell';
44
const GET_SYSTEM_TIME_COMMAND = pwsh$ /* ps1 */ `(Get-Date).ToString(${0})`;
55
const ISO_8061_FORMAT = 'yyyy-MM-ddTHH:mm:sszzz';
66

7-
export async function getDeviceTime(this: NovaWindowsDriver, format?: string): Promise<string> {
8-
format = format ? new PSString(format).toString() : `'${ISO_8061_FORMAT}'`;
9-
return await this.sendPowerShellCommand(GET_SYSTEM_TIME_COMMAND.format(format));
7+
export async function getDeviceTime(this: NovaWindowsDriver, _sessionId?: string, format?: string): Promise<string> {
8+
const fmt = format ? new PSString(format).toString() : `'${ISO_8061_FORMAT}'`;
9+
return await this.sendPowerShellCommand(GET_SYSTEM_TIME_COMMAND.format(fmt));
1010
}
1111

1212
// command: 'hideKeyboard'

0 commit comments

Comments
 (0)