-
-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: update types for documentation
- Loading branch information
Showing
23 changed files
with
2,418 additions
and
1,565 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.