Skip to content

Commit

Permalink
chore: update types for documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
boneskull committed Jul 3, 2023
1 parent 93a8750 commit 2d7231c
Show file tree
Hide file tree
Showing 23 changed files with 2,418 additions and 1,565 deletions.
136 changes: 75 additions & 61 deletions lib/commands/actions.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,88 @@
// @ts-check

import {mixin} from './mixins';

let commands = {}, helpers = {}, extensions = {};
/**
* @type {import('./mixins').UIA2ActionsMixin}
* @satisfies {import('@appium/types').ExternalDriver}
*/
const ActionsMixin = {
async pressKeyCode(keycode, metastate, flags) {
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
'/appium/device/press_keycode',
'POST',
{
keycode,
metastate,
flags,
}
);
},

commands.pressKeyCode = async function (keycode, metastate = null, flags = null) {
return await this.uiautomator2.jwproxy.command('/appium/device/press_keycode', 'POST', {
keycode,
metastate,
flags,
});
};
async longPressKeyCode(keycode, metastate, flags) {
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
'/appium/device/long_press_keycode',
'POST',
{
keycode,
metastate,
flags,
}
);
},

commands.longPressKeyCode = async function (keycode, metastate = null, flags = null) {
return await this.uiautomator2.jwproxy.command('/appium/device/long_press_keycode', 'POST', {
keycode,
metastate,
flags
});
};
async doSwipe(swipeOpts) {
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
`/touch/perform`,
'POST',
swipeOpts
);
},

commands.doSwipe = async function (swipeOpts) {
return await this.uiautomator2.jwproxy.command(`/touch/perform`, 'POST', swipeOpts);
};
async doDrag(dragOpts) {
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
`/touch/drag`,
'POST',
dragOpts
);
},

commands.doDrag = async function (dragOpts) {
return await this.uiautomator2.jwproxy.command(`/touch/drag`, 'POST', dragOpts);
};
async getOrientation() {
return /** @type {import('@appium/types').Orientation} */ (
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
`/orientation`,
'GET',
{}
)
);
},

commands.getOrientation = async function () {
return await this.uiautomator2.jwproxy.command(`/orientation`, 'GET', {});
};
async setOrientation(orientation) {
orientation = /** @type {import('@appium/types').Orientation} */ (orientation.toUpperCase());
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
`/orientation`,
'POST',
{orientation}
);
},

async mobilePressKey(opts) {
const {keycode, metastate, flags, isLongPress = false} = opts;

commands.setOrientation = async function (orientation) {
orientation = orientation.toUpperCase();
return await this.uiautomator2.jwproxy.command(`/orientation`, 'POST', {orientation});
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
`/appium/device/${isLongPress ? 'long_' : ''}press_keycode`,
'POST',
{
keycode,
metastate,
flags,
}
);
},
};

/**
* @typedef {Object} PressKeyOptions
* @property {number} keycode A valid Android key code. See https://developer.android.com/reference/android/view/KeyEvent
* for the list of available key codes
* @property {number?} metastate An integer in which each bit set to 1 represents a pressed meta key. See
* https://developer.android.com/reference/android/view/KeyEvent for more details.
* @property {string?} flags Flags for the particular key event. See
* https://developer.android.com/reference/android/view/KeyEvent for more details.
* @property {boolean} isLongPress [false] Whether to emulate long key press
*/
mixin(ActionsMixin);

/**
* Emulates single key press of the key with the given code.
*
* @param {PressKeyOptions} opts
* @typedef {import('../uiautomator2').UiAutomator2Server} UiAutomator2Server
*/
commands.mobilePressKey = async function mobilePressKey(opts = {}) {
const {
keycode,
metastate,
flags,
isLongPress = false,
} = opts;

return await this.uiautomator2.jwproxy.command(
`/appium/device/${isLongPress ? 'long_' : ''}press_keycode`,
'POST', {
keycode,
metastate,
flags
}
);
};

Object.assign(extensions, commands, helpers);
export { commands, helpers };
export default extensions;
80 changes: 36 additions & 44 deletions lib/commands/alert.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,45 @@
let commands = {}, helpers = {}, extensions = {};
// @ts-check

commands.getAlertText = async function () {
return await this.uiautomator2.jwproxy.command('/alert/text', 'GET', {});
};

/**
* @typedef {Object} AcceptAlertOptions
* @property {?string} buttonLabel - The name of the button to click in order to
* accept the alert. If the name is not provided
* then the script will try to detect the button
* automatically.
*/
import {mixin} from './mixins';

/**
* @param {AcceptAlertOptions} opts
* @throws {InvalidElementStateError} If no matching button, that can accept the alert,
* can be found
* @throws {NoAlertOpenError} If no alert is present
* @type {import('./mixins').UIA2AlertMixin}
* @satisfies {import('@appium/types').ExternalDriver}
*/
commands.mobileAcceptAlert = async function (opts = {}) {
return await this.uiautomator2.jwproxy.command('/alert/accept', 'POST', opts);
};

commands.postAcceptAlert = async function () {
return await this.mobileAcceptAlert();
const AlertMixin = {
async getAlertText() {
return String(
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
'/alert/text',
'GET',
{}
)
);
},
async mobileAcceptAlert(opts = {}) {
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
'/alert/accept',
'POST',
opts
);
},
async postAcceptAlert() {
await this.mobileAcceptAlert();
},
async mobileDismissAlert(opts = {}) {
await /** @type {UiAutomator2Server} */ (this.uiautomator2).jwproxy.command(
'/alert/dismiss',
'POST',
opts
);
},
async postDismissAlert() {
await this.mobileDismissAlert();
},
};

/**
* @typedef {Object} DismissAlertOptions
* @property {?string} buttonLabel - The name of the button to click in order to
* dismiss the alert. If the name is not provided
* then the script will try to detect the button
* automatically.
*/
mixin(AlertMixin);

/**
* @param {DismissAlertOptions} opts
* @throws {InvalidElementStateError} If no matching button, that can dismiss the alert,
* can be found
* @throws {NoAlertOpenError} If no alert is present
* @typedef {import('../uiautomator2').UiAutomator2Server} UiAutomator2Server
*/
commands.mobileDismissAlert = async function (opts = {}) {
return await this.uiautomator2.jwproxy.command('/alert/dismiss', 'POST', opts);
};

commands.postDismissAlert = async function () {
return await this.mobileDismissAlert();
};

Object.assign(extensions, commands, helpers);
export { commands, helpers };
export default extensions;
137 changes: 79 additions & 58 deletions lib/commands/app-strings.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,97 @@
// @ts-check

import {mixin} from './mixins';
import _ from 'lodash';
import { fs, tempDir } from 'appium/support';
import {fs, tempDir} from 'appium/support';

const commands = {};
/**
* @type {import('./mixins').UIA2AppStringsMixin}
* @satisfies {import('@appium/types').ExternalDriver}
*/
const AppStringsMixin = {
async getStrings(language) {
const adb = /** @type {ADB} */ (this.adb);
if (!language) {
language = await adb.getDeviceLanguage();
this.log.info(`No language specified, returning strings for: ${language}`);
}

commands.getStrings = async function (language) {
if (!language) {
language = await this.adb.getDeviceLanguage();
this.log.info(`No language specified, returning strings for: ${language}`);
}
/**
* Clients require the resulting mapping to have both keys
* and values of type string
* @param {StringRecord} mapping
*/
const preprocessStringsMap = function (mapping) {
/** @type {StringRecord} */
const result = {};
for (const [key, value] of _.toPairs(mapping)) {
result[key] = _.isString(value) ? value : JSON.stringify(value);
}
return result;
};

// Clients require the resulting mapping to have both keys
// and values of type string
const preprocessStringsMap = function (mapping) {
const result = {};
for (const [key, value] of _.toPairs(mapping)) {
result[key] = _.isString(value) ? value : JSON.stringify(value);
if (this.apkStrings[language]) {
// Return cached strings
return preprocessStringsMap(this.apkStrings[language]);
}
return result;
};

if (this.apkStrings[language]) {
// Return cached strings
return preprocessStringsMap(this.apkStrings[language]);
}
if (!this.opts.app && !this.opts.appPackage) {
this.log.errorAndThrow("One of 'app' or 'appPackage' capabilities should must be specified");
throw new Error(); // unreachable
}

let app = this.opts.app;
const tmpRoot = await tempDir.openDir();
try {
if (!app) {
try {
app = await adb.pullApk(/** @type {string} */ (this.opts.appPackage), tmpRoot);
} catch (err) {
this.log.errorAndThrow(
`Failed to pull an apk from '${this.opts.appPackage}'. Original error: ${
/** @type {Error} */ (err).message
}`
);
throw new Error(); // unreachable
}
}

if (!this.opts.app && !this.opts.appPackage) {
this.log.errorAndThrow("One of 'app' or 'appPackage' capabilities should must be specified");
}
if (!(await fs.exists(app))) {
this.log.errorAndThrow(`The app at '${app}' does not exist`);
throw new Error(); // unreachable
}

let app = this.opts.app;
const tmpRoot = await tempDir.openDir();
try {
if (!app) {
try {
app = await this.adb.pullApk(this.opts.appPackage, tmpRoot);
const {apkStrings} = await adb.extractStringsFromApk(app, language, tmpRoot);
this.apkStrings[language] = apkStrings;
return preprocessStringsMap(apkStrings);
} catch (err) {
this.log.errorAndThrow(`Failed to pull an apk from '${this.opts.appPackage}'. Original error: ${err.message}`);
this.log.errorAndThrow(
`Cannot extract strings from '${app}'. Original error: ${
/** @type {Error} */ (err).message
}`
);
throw new Error(); // unreachable
}
} finally {
await fs.rimraf(tmpRoot);
}
},

if (!await fs.exists(app)) {
this.log.errorAndThrow(`The app at '${app}' does not exist`);
}

try {
const {apkStrings} = await this.adb.extractStringsFromApk(app, language, tmpRoot);
this.apkStrings[language] = apkStrings;
return preprocessStringsMap(apkStrings);
} catch (err) {
this.log.errorAndThrow(`Cannot extract strings from '${app}'. Original error: ${err.message}`);
}
} finally {
await fs.rimraf(tmpRoot);
}
/**
* Retrives app strings from its resources for the given language
* or the default device language.
*
* @returns App strings map
*/
async mobileGetAppStrings(opts) {
return await this.getStrings(opts?.language);
},
};

/**
* @typedef {Object} GetAppStringsOptions
* @property {string?} language The language abbreviation to fetch app strings mapping for. If no
* language is provided then strings for the default language on the device under test
* would be returned. Examples: en, fr
*/
mixin(AppStringsMixin);

/**
* Retrives app strings from its resources for the given language
* or the default device language.
*
* @param {GetAppStringsOptions} opts
* @returns {Promise<object>} App strings map
* @typedef {import('appium-adb').ADB} ADB
* @typedef {import('@appium/types').StringRecord} StringRecord
*/
commands.mobileGetAppStrings = async function mobileGetAppStrings (opts = {}) {
return await this.getStrings(opts.language);
};

export default commands;
Loading

0 comments on commit 2d7231c

Please sign in to comment.