Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@ jobs:
name: Install dev dependencies
- run: npm run lint
name: Run linter
- run: npm run format:check
name: Run Prettier check
- run: npm run test
name: Run unit tests
12 changes: 4 additions & 8 deletions lib/commands/app-management.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
*
* @this {WindowsDriver}
*/
export async function windowsLaunchApp () {
return await this.winAppDriver.sendCommand(
'/appium/app/launch', 'POST', {}
);
export async function windowsLaunchApp() {
return await this.winAppDriver.sendCommand('/appium/app/launch', 'POST', {});
}

/**
Expand All @@ -30,10 +28,8 @@ export async function windowsLaunchApp () {
* @this {WindowsDriver}
* @throws {Error} if the app process is not running
*/
export async function windowsCloseApp () {
return await this.winAppDriver.sendCommand(
'/appium/app/close', 'POST', {}
);
export async function windowsCloseApp() {
return await this.winAppDriver.sendCommand('/appium/app/close', 'POST', {});
}

/**
Expand Down
41 changes: 21 additions & 20 deletions lib/commands/clipboard.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { exec } from 'teen_process';
import { errors } from 'appium/driver';
import {exec} from 'teen_process';
import {errors} from 'appium/driver';
import _ from 'lodash';

/**
Expand All @@ -23,29 +23,30 @@ const CONTENT_TYPE = Object.freeze({
* @param {string} b64Content base64-encoded clipboard content to set
* @param {ContentTypeEnum} [contentType='text'] The clipboard content type to set
*/
export async function windowsSetClipboard (
b64Content,
contentType = CONTENT_TYPE.plaintext
) {
export async function windowsSetClipboard(b64Content, contentType = CONTENT_TYPE.plaintext) {
if (b64Content && Buffer.from(b64Content, 'base64').toString('base64') !== b64Content) {
throw new errors.InvalidArgumentError(`The 'b64Content' argument must be a valid base64-encoded string`);
throw new errors.InvalidArgumentError(
`The 'b64Content' argument must be a valid base64-encoded string`,
);
}
switch (contentType) {
case CONTENT_TYPE.plaintext:
return await exec('powershell', ['-command',
return await exec('powershell', [
'-command',
`$str=[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${b64Content}'));`,
'Set-Clipboard -Value $str'
'Set-Clipboard -Value $str',
]);
case CONTENT_TYPE.image:
return await exec('powershell', ['-command',
return await exec('powershell', [
'-command',
`$img=[Drawing.Bitmap]::FromStream([IO.MemoryStream][Convert]::FromBase64String('${b64Content}'));`,
'[System.Windows.Forms.Clipboard]::SetImage($img);',
'$img.Dispose();'
'$img.Dispose();',
]);
default:
throw new errors.InvalidArgumentError(
`The clipboard content type '${contentType}' is not known. ` +
`Only the following content types are supported: ${_.values(CONTENT_TYPE)}`
`Only the following content types are supported: ${_.values(CONTENT_TYPE)}`,
);
}
}
Expand All @@ -60,29 +61,29 @@ export async function windowsSetClipboard (
* Only PNG images are supported for extraction if set to 'image'.
* @returns {Promise<string>} base64-encoded content of the clipboard
*/
export async function windowsGetClipboard (
contentType = CONTENT_TYPE.plaintext
) {
export async function windowsGetClipboard(contentType = CONTENT_TYPE.plaintext) {
switch (contentType) {
case CONTENT_TYPE.plaintext: {
const {stdout} = await exec('powershell', ['-command',
const {stdout} = await exec('powershell', [
'-command',
'$str=Get-Clipboard;',
'[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($str));'
'[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($str));',
]);
return _.trim(stdout);
}
case CONTENT_TYPE.image: {
const {stdout} = await exec('powershell', ['-command',
const {stdout} = await exec('powershell', [
'-command',
'$s=New-Object System.IO.MemoryStream;',
'[System.Windows.Forms.Clipboard]::GetImage().Save($s,[System.Drawing.Imaging.ImageFormat]::Png);',
'[System.Convert]::ToBase64String($s.ToArray());'
'[System.Convert]::ToBase64String($s.ToArray());',
]);
return _.trim(stdout);
}
default:
throw new errors.InvalidArgumentError(
`The clipboard content type '${contentType}' is not known. ` +
`Only the following content types are supported: ${_.values(CONTENT_TYPE)}`
`Only the following content types are supported: ${_.values(CONTENT_TYPE)}`,
);
}
}
Expand Down
12 changes: 6 additions & 6 deletions lib/commands/context.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { errors } from 'appium/driver';
import {errors} from 'appium/driver';

const WINDOWS_CONTEXT = 'NATIVE_APP';

export async function getContexts (): Promise<string[]> {
export async function getContexts(): Promise<string[]> {
return [WINDOWS_CONTEXT];
}

export async function getCurrentContext (): Promise<string> {
export async function getCurrentContext(): Promise<string> {
return WINDOWS_CONTEXT;
}

export async function setContext (context: string): Promise<void> {
export async function setContext(context: string): Promise<void> {
if (context !== WINDOWS_CONTEXT) {
throw new errors.NoSuchContextError(
`The Windows Driver only supports '${WINDOWS_CONTEXT}' context.`
`The Windows Driver only supports '${WINDOWS_CONTEXT}' context.`,
);
}
}
}
8 changes: 5 additions & 3 deletions lib/commands/execute.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from 'lodash';
import { POWER_SHELL_FEATURE } from '../constants';
import {POWER_SHELL_FEATURE} from '../constants';

const POWER_SHELL_SCRIPT = 'powerShell';
const EXECUTE_SCRIPT_PREFIX = 'windows:';
Expand All @@ -11,11 +11,13 @@ const EXECUTE_SCRIPT_PREFIX = 'windows:';
* @param {ExecuteMethodArgs} [args]
* @returns {Promise<any>}
*/
export async function execute (script, args) {
export async function execute(script, args) {
if (script === POWER_SHELL_SCRIPT) {
this.assertFeatureEnabled(POWER_SHELL_FEATURE);
return await this.execPowerShell(
/** @type {import('./powershell').ExecPowerShellOptions} */ (preprocessExecuteMethodArgs(args))
/** @type {import('./powershell').ExecPowerShellOptions} */ (
preprocessExecuteMethodArgs(args)
),
);
}

Expand Down
73 changes: 42 additions & 31 deletions lib/commands/file-movement.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import _ from 'lodash';
import path from 'node:path';
import { errors } from 'appium/driver';
import { fs, mkdirp, util, zip } from 'appium/support';
import { MODIFY_FS_FEATURE } from '../constants';
import {errors} from 'appium/driver';
import {fs, mkdirp, util, zip} from 'appium/support';
import {MODIFY_FS_FEATURE} from '../constants';

// List of env variables, that can be expanded in path
const KNOWN_ENV_VARS = [
'APPDATA', 'LOCALAPPDATA',
'PROGRAMFILES', 'PROGRAMFILES(X86)',
'PROGRAMDATA', 'ALLUSERSPROFILE',
'TEMP', 'TMP',
'HOMEPATH', 'USERPROFILE', 'PUBLIC'
'APPDATA',
'LOCALAPPDATA',
'PROGRAMFILES',
'PROGRAMFILES(X86)',
'PROGRAMDATA',
'ALLUSERSPROFILE',
'TEMP',
'TMP',
'HOMEPATH',
'USERPROFILE',
'PUBLIC',
];

/**
Expand All @@ -20,12 +26,13 @@ const KNOWN_ENV_VARS = [
* @param {string} base64Data
* @returns {Promise<void>}
*/
export async function pushFile (remotePath, base64Data) {
export async function pushFile(remotePath, base64Data) {
this.assertFeatureEnabled(MODIFY_FS_FEATURE);
if (remotePath.endsWith(path.sep)) {
throw new errors.InvalidArgumentError(
'It is expected that remote path points to a file rather than a folder. ' +
`'${remotePath}' is given instead`);
`'${remotePath}' is given instead`,
);
}

if (_.isArray(base64Data)) {
Expand All @@ -46,7 +53,7 @@ export async function pushFile (remotePath, base64Data) {
* @param {string} remotePath
* @returns {Promise<string>}
*/
export async function pullFile (remotePath) {
export async function pullFile(remotePath) {
const fullPath = resolveToAbsolutePath(remotePath);
await checkFileExists(fullPath);
return (await util.toInMemoryBase64(fullPath)).toString();
Expand All @@ -58,12 +65,14 @@ export async function pullFile (remotePath) {
* @param {string} remotePath
* @returns {Promise<string>}
*/
export async function pullFolder (remotePath) {
export async function pullFolder(remotePath) {
const fullPath = resolveToAbsolutePath(remotePath);
await checkFolderExists(fullPath);
return (await zip.toInMemoryZip(fullPath, {
encodeToBase64: true,
})).toString();
return (
await zip.toInMemoryZip(fullPath, {
encodeToBase64: true,
})
).toString();
}

/**
Expand All @@ -78,7 +87,7 @@ export async function pullFolder (remotePath) {
* @throws {InvalidArgumentError} If the file to be deleted does not exist or
* remote path is not an absolute path.
*/
export async function windowsDeleteFile (remotePath) {
export async function windowsDeleteFile(remotePath) {
this.assertFeatureEnabled(MODIFY_FS_FEATURE);
const fullPath = resolveToAbsolutePath(remotePath);
await checkFileExists(fullPath);
Expand All @@ -97,7 +106,7 @@ export async function windowsDeleteFile (remotePath) {
* @throws {InvalidArgumentError} If the folder to be deleted does not exist or
* remote path is not an absolute path.
*/
export async function windowsDeleteFolder (remotePath) {
export async function windowsDeleteFolder(remotePath) {
this.assertFeatureEnabled(MODIFY_FS_FEATURE);
const fullPath = resolveToAbsolutePath(remotePath);
await checkFolderExists(fullPath);
Expand All @@ -109,17 +118,17 @@ export async function windowsDeleteFolder (remotePath) {
* @param {string} remotePath
* @returns {string}
*/
function resolveToAbsolutePath (remotePath) {
const resolvedPath = remotePath.replace(
/%([^%]+)%/g,
(_, key) => KNOWN_ENV_VARS.includes(key.toUpperCase())
function resolveToAbsolutePath(remotePath) {
const resolvedPath = remotePath.replace(/%([^%]+)%/g, (_, key) =>
KNOWN_ENV_VARS.includes(key.toUpperCase())
? /** @type {string} */ (process.env[key.toUpperCase()])
: `%${key}%`
: `%${key}%`,
);

if (!path.isAbsolute(resolvedPath)) {
throw new errors.InvalidArgumentError('It is expected that remote path is absolute. ' +
`'${resolvedPath}' is given instead`);
throw new errors.InvalidArgumentError(
'It is expected that remote path is absolute. ' + `'${resolvedPath}' is given instead`,
);
}
return resolvedPath;
}
Expand All @@ -129,15 +138,16 @@ function resolveToAbsolutePath (remotePath) {
* @param {string} remotePath
* @returns {Promise<void>}
*/
async function checkFileExists (remotePath) {
if (!await fs.exists(remotePath)) {
async function checkFileExists(remotePath) {
if (!(await fs.exists(remotePath))) {
throw new errors.InvalidArgumentError(`The remote file '${remotePath}' does not exist.`);
}
const stat = await fs.stat(remotePath);
if (!stat.isFile()) {
throw new errors.InvalidArgumentError(
'It is expected that remote path points to a file rather than a folder. ' +
`'${remotePath}' is given instead`);
`'${remotePath}' is given instead`,
);
}
}

Expand All @@ -146,18 +156,19 @@ async function checkFileExists (remotePath) {
* @param {string} remotePath
* @returns {Promise<void>}
*/
async function checkFolderExists (remotePath) {
if (!await fs.exists(remotePath)) {
async function checkFolderExists(remotePath) {
if (!(await fs.exists(remotePath))) {
throw new errors.InvalidArgumentError(`The remote folder '${remotePath}' does not exist.`);
}
const stat = await fs.stat(remotePath);
if (!stat.isDirectory()) {
throw new errors.InvalidArgumentError(
'It is expected that remote path points to a folder rather than a file. ' +
`'${remotePath}' is given instead`);
`'${remotePath}' is given instead`,
);
}
}

/**
* @typedef {import('../driver').WindowsDriver} WindowsDriver
*/
*/
4 changes: 2 additions & 2 deletions lib/commands/find.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { util } from 'appium/support';
import {util} from 'appium/support';

/**
*
Expand All @@ -9,7 +9,7 @@ import { util } from 'appium/support';
* @param {string} [context]
* @returns
*/
export async function findElOrEls (strategy, selector, mult, context) {
export async function findElOrEls(strategy, selector, mult, context) {
const endpoint = `/element${context ? `/${util.unwrapElement(context)}/element` : ''}${mult ? 's' : ''}`;
// This is either an array if mult is true or an object if mult is false
return await this.winAppDriver.sendCommand(endpoint, 'POST', {
Expand Down
Loading