chore(fake-driver): use "mixin" style class composition; many type fixes
MODIFY
packages/fake-driver/lib/commands/alert.js
packages/fake-driver/lib/commands/alert.js
import {errors} from 'appium/driver';
-let commands = {},
- helpers = {},
- extensions = {};
-
-helpers.assertNoAlert = function assertNoAlert() {
+/**
+ * @template {Class<import('../types').IContextsCommands & import('@appium/types').IFindCommands>} T
+ * @param {T} Base
+ * @returns {Class<AlertCommands>}
+ */
+export function AlertMixin(Base) {
+ /**
+ * @implements {IAlertCommands}
+ */
+ class AlertCommands extends Base {
+ assertNoAlert() {
if (this.appModel.hasAlert()) {
throw new errors.UnexpectedAlertOpenError();
}
-};
+ }
-helpers.assertAlert = function assertAlert() {
+ assertAlert() {
if (!this.appModel.hasAlert()) {
throw new errors.NoAlertOpenError();
}
-};
+ }
/**
* Get the text of an alert
*
- * @appiumCommand
* @returns {Promise<string>}
*/
-commands.getAlertText = async function getAlertText() {
+ async getAlertText() {
this.assertAlert();
return this.appModel.alertText();
-};
+ }
/**
* Set the text of an alert
*
- * @appiumCommand
* @param {string} text
- * @returns {Promise<null>}
+ * @returns {Promise<void>}
*/
-commands.setAlertText = async function setAlertText(text) {
+ async setAlertText(text) {
this.assertAlert();
try {
this.appModel.setAlertText(text);
} catch (e) {
throw new errors.InvalidElementStateError();
}
-};
+ }
/**
* Accept an alert
*
- * @appiumCommand
- * @returns {Promise<null>}
+ * @returns {Promise<void>}
*/
-commands.postAcceptAlert = async function postAcceptAlert() {
+ async postAcceptAlert() {
this.assertAlert();
this.appModel.handleAlert();
-};
+ }
/**
* Dismiss an alert
*
- * @appiumCommand
* @returns {Promise<null>}
*/
-commands.postDismissAlert = commands.postAcceptAlert;
+ postDismissAlert = this.postAcceptAlert;
+ }
+
+ return AlertCommands;
+}
+
+/**
+ * @typedef {import('../driver').FakeDriverCore} FakeDriverCore
+ */
-Object.assign(extensions, commands, helpers);
-export {commands, helpers};
-export default extensions;
+/**
+ * @template T,[U={}],[V=Array<any>]
+ * @typedef {import('@appium/types').Class<T,U,V>} Class
+ */
+
+/**
+ * @typedef {import('../types').IAlertCommands} IAlertCommands
+ */

MODIFY
packages/fake-driver/lib/commands/contexts.js
packages/fake-driver/lib/commands/contexts.js
import _ from 'lodash';
import {errors} from 'appium/driver';
-let commands = {},
- helpers = {},
- extensions = {};
-
-helpers.getRawContexts = function getRawContexts() {
+/**
+ *
+ * @template {Class<import('../types').IFakeDriver>} T
+ * @param {T} Base
+ */
+export function ContextsMixin(Base) {
+ /**
+ * @implements {IContextsCommands}
+ */
+ class ContextsCommands extends Base {
+ getRawContexts() {
let contexts = {NATIVE_APP: null, PROXY: null};
- let wvs = this.appModel.getWebviews();
+ let wvs = this.appModel?.getWebviews() ?? [];
for (let i = 1; i < wvs.length + 1; i++) {
contexts[`WEBVIEW_${i}`] = wvs[i - 1];
}
return contexts;
-};
+ }
-helpers.assertWebviewContext = function assertWebviewContext() {
+ assertWebviewContext() {
if (this.curContext === 'NATIVE_APP') {
throw new errors.InvalidContextError();
}
-};
+ }
/**
* Get the current appium context
*
- * @appiumCommand
* @returns {Promise<string>}
*/
-commands.getCurrentContext = async function getCurrentContext() {
+ async getCurrentContext() {
return this.curContext;
-};
+ }
/**
* Get the list of available contexts
*
- * @appiumCommand
- * @returns {Promise<Array<string>>}
+ * @returns {Promise<string[]>}
*/
-commands.getContexts = async function getContexts() {
+ async getContexts() {
return _.keys(this.getRawContexts());
-};
+ }
/**
* Set the current context
*
- * @appiumCommand
* @param {string} context - name of the context
- * @returns {Promise<null>}
+ * @returns {Promise<void>}
*/
-commands.setContext = async function setContext(context) {
+ async setContext(context) {
let contexts = this.getRawContexts();
- if (_.includes(_.keys(contexts), context)) {
+ if (context in contexts) {
this.curContext = context;
if (context === 'NATIVE_APP') {
this.appModel.deactivateWebview();
@@ -63,16 +66,15 @@ commands.setContext = async function setContext(context) {
} else {
throw new errors.NoSuchContextError();
}
-};
+ }
/**
* Set the active frame
*
- * @appiumCommand
- * @param {number}
- * @returns {Promise<null>}
+ * @param {number} frameId
+ * @returns {Promise<void>}
*/
-commands.setFrame = async function setFrame(frameId) {
+ async setFrame(frameId) {
this.assertWebviewContext();
if (frameId === null) {
this.appModel.deactivateFrame();
@@ -83,8 +85,18 @@ commands.setFrame = async function setFrame(frameId) {
}
this.appModel.activateFrame(nodes[0]);
}
-};
+ }
+ }
+
+ return ContextsCommands;
+}
-Object.assign(extensions, commands, helpers);
-export {commands, helpers};
-export default extensions;
+/**
+ * @typedef {import('../driver').FakeDriverCore} FakeDriverCore
+ * @typedef {import('../types').IContextsCommands} IContextsCommands
+ */
+
+/**
+ * @template T,[U={}],[V=Array<any>]
+ * @typedef {import('@appium/types').Class<T,U,V>} Class
+ */

MODIFY
packages/fake-driver/lib/commands/element.js
packages/fake-driver/lib/commands/element.js
import _ from 'lodash';
import {errors} from 'appium/driver';
-let commands = {},
- helpers = {},
- extensions = {};
-
-helpers.getElements = function getElements(elIds) {
+/**
+ * @template {Class<import('../types').IContextsCommands & import('../types').IAlertCommands>} T
+ * @param {T} Base
+ */
+export function ElementMixin(Base) {
+ /**
+ * @implements {IElementCommands}
+ */
+ class ElementCommands extends Base {
+ getElements(elIds) {
for (let elId of elIds) {
if (!_.has(this.elMap, elId)) {
throw new errors.StaleElementReferenceError();
}
}
return elIds.map((e) => this.elMap[e]);
-};
+ }
-helpers.getElement = function getElement(elId) {
+ getElement(elId) {
return this.getElements([elId])[0];
-};
+ }
-commands.getName = async function getName(elementId) {
+ async getName(elementId) {
let el = this.getElement(elementId);
return el.tagName;
-};
+ }
-commands.elementDisplayed = async function elementDisplayed(elementId) {
+ async elementDisplayed(elementId) {
let el = this.getElement(elementId);
return el.isVisible();
-};
+ }
-commands.elementEnabled = async function elementEnabled(elementId) {
+ async elementEnabled(elementId) {
let el = this.getElement(elementId);
return el.isEnabled();
-};
+ }
-commands.elementSelected = async function elementSelected(elementId) {
+ async elementSelected(elementId) {
let el = this.getElement(elementId);
return el.isSelected();
-};
+ }
-commands.setValue = async function setValue(keys, elementId) {
+ async setValue(keys, elementId) {
let value = keys;
if (keys instanceof Array) {
value = keys.join('');
@@ -48,18 +53,18 @@ commands.setValue = async function setValue(keys, elementId) {
throw new errors.InvalidElementStateError();
}
el.setAttr('value', value);
-};
+ }
-commands.getText = async function getText(elementId) {
+ async getText(elementId) {
let el = this.getElement(elementId);
return el.getAttr('value');
-};
+ }
-commands.clear = async function clear(elementId) {
+ async clear(elementId) {
await this.setValue('', elementId);
-};
+ }
-commands.click = async function click(elementId) {
+ async click(elementId) {
this.assertNoAlert();
let el = this.getElement(elementId);
if (!el.isVisible()) {
@@ -67,37 +72,45 @@ commands.click = async function click(elementId) {
}
el.click();
this.focusedElId = elementId;
-};
+ }
-commands.getAttribute = async function getAttribute(attr, elementId) {
+ async getAttribute(attr, elementId) {
let el = this.getElement(elementId);
return el.getAttr(attr);
-};
-
-commands.getElementRect = function getElementRect(elementId) {
+ }
+ getElementRect(elementId) {
let el = this.getElement(elementId);
return el.getElementRect();
-};
-
-commands.getSize = function getSize(elementId) {
+ }
+ getSize(elementId) {
let el = this.getElement(elementId);
return el.getSize();
-};
+ }
-commands.equalsElement = function equalsElement(el1Id, el2Id) {
+ equalsElement(el1Id, el2Id) {
let el1 = this.getElement(el1Id);
let el2 = this.getElement(el2Id);
return el1.equals(el2);
-};
-
-commands.getLocationInView = commands.getLocation;
+ }
-commands.getCssProperty = async function getCssProperty(prop, elementId) {
+ async getCssProperty(prop, elementId) {
this.assertWebviewContext();
let el = this.getElement(elementId);
return el.getCss(prop);
-};
+ }
+
+ getLocationInView = this.getLocation;
+ }
+
+ return ElementCommands;
+}
+
+/**
+ * @typedef {import('../driver').FakeDriverCore} FakeDriverCore
+ * @typedef {import('../types').IElementCommands} IElementCommands
+ */
-Object.assign(extensions, commands, helpers);
-export {commands, helpers};
-export default extensions;
+/**
+ * @template T,[U={}],[V=Array<any>]
+ * @typedef {import('@appium/types').Class<T,U,V>} Class
+ */

MODIFY
packages/fake-driver/lib/commands/find.js
packages/fake-driver/lib/commands/find.js
@@ -2,20 +2,25 @@ import _ from 'lodash';
import {errors} from 'appium/driver';
import {FakeElement} from '../fake-element';
-let commands = {},
- helpers = {},
- extensions = {};
-
-helpers.getExistingElementForNode = function getExistingElementForNode(node) {
+/**
+ * @template {Class<import('../types').IElementCommands>} T
+ * @param {T} Base
+ */
+export function FindMixin(Base) {
+ /**
+ * @implements {IFindCommands}
+ */
+ class FindCommands extends Base {
+ getExistingElementForNode(node) {
for (let [id, el] of _.toPairs(this.elMap)) {
if (el.node === node) {
return id;
}
}
return null;
-};
+ }
-helpers.wrapNewEl = function wrapNewEl(obj) {
+ wrapNewEl(obj) {
// first check and see if we already have a ref to this element
let existingElId = this.getExistingElementForNode(obj);
if (existingElId) {
@@ -26,9 +31,17 @@ helpers.wrapNewEl = function wrapNewEl(obj) {
this.maxElId++;
this.elMap[this.maxElId.toString()] = new FakeElement(obj, this.appModel);
return {ELEMENT: this.maxElId.toString()};
-};
-
-helpers.findElOrEls = async function findElOrEls(strategy, selector, mult, ctx) {
+ }
+ /**
+ * @template {boolean} Mult
+ * @template [Ctx=any]
+ * @param {string} strategy
+ * @param {string} selector
+ * @param {Mult} mult
+ * @param {Ctx} [context]
+ * @returns {Promise<Mult extends true ? Element[] : Element>}
+ */
+ async findElOrEls(strategy, selector, mult, context) {
let qMap = {
xpath: 'xpathQuery',
id: 'idQuery',
@@ -44,50 +57,61 @@ helpers.findElOrEls = async function findElOrEls(strategy, selector, mult, ctx)
if (selector === 'badsel') {
throw new errors.InvalidSelectorError();
}
- let els = this.appModel[qMap[strategy]](selector, ctx);
+ let els = this.appModel[qMap[strategy]](selector, context);
+
+ let retval;
if (els.length) {
if (mult) {
let allEls = [];
for (let el of els) {
allEls.push(this.wrapNewEl(el));
}
- return allEls;
+ retval = allEls;
} else {
- return this.wrapNewEl(els[0]);
+ retval = this.wrapNewEl(els[0]);
}
} else if (mult) {
- return [];
+ retval = [];
} else {
throw new errors.NoSuchElementError();
}
-};
+ return /** @type {Mult extends true ? Element[] : Element} */ (retval);
+ }
-commands.findElement = async function findElement(strategy, selector) {
+ async findElement(strategy, selector) {
return this.findElOrEls(strategy, selector, false);
-};
+ }
-commands.findElements = async function findElements(strategy, selector) {
+ async findElements(strategy, selector) {
return this.findElOrEls(strategy, selector, true);
-};
+ }
-commands.findElementFromElement = async function findElementFromElement(
- strategy,
- selector,
- elementId
-) {
+ async findElementFromElement(strategy, selector, elementId) {
let el = this.getElement(elementId);
return this.findElOrEls(strategy, selector, false, el.xmlFragment);
-};
+ }
-commands.findElementsFromElement = async function findElementsFromElement(
- strategy,
- selector,
- elementId
-) {
+ async findElementsFromElement(strategy, selector, elementId) {
let el = this.getElement(elementId);
return this.findElOrEls(strategy, selector, true, el.xmlFragment);
-};
+ }
+ }
+
+ return FindCommands;
+}
+
+/**
+ * @typedef {import('../driver').FakeDriverCore} FakeDriverCore
+ * @typedef {import('@appium/types').Element} Element
+
+ */
+
+/**
+ * @template T,[U={}],[V=Array<any>]
+ * @typedef {import('@appium/types').Class<T,U,V>} Class
+ */
-Object.assign(extensions, commands, helpers);
-export {commands, helpers};
-export default extensions;
+/**
+ * @template [Ctx=any]
+ * @typedef {import('@appium/types').IFindCommands<Ctx>} IFindCommands
+ */

MODIFY
packages/fake-driver/lib/commands/general.js
packages/fake-driver/lib/commands/general.js
-import _ from 'lodash';
import {errors} from 'appium/driver';
-let commands = {},
- helpers = {},
- extensions = {};
+const ORIENTATIONS = new Set(['LANDSCAPE', 'PORTRAIT']);
-commands.title = async function title() {
+/**
+ * @template {Class<import('../types').IContextsCommands & import('../types').IElementCommands>} T
+ * @param {T} Base
+ */
+export function GeneralMixin(Base) {
+ /**
+ * @implements {IGeneralCommands}
+ */
+ class GeneralCommands extends Base {
+ async title() {
this.assertWebviewContext();
return this.appModel.title;
-};
+ }
-commands.keys = async function keys(value) {
+ async keys(value) {
if (!this.focusedElId) {
throw new errors.InvalidElementStateError();
}
await this.setValue(value, this.focusedElId);
-};
+ }
-commands.setGeoLocation = async function setGeoLocation(location) {
+ async setGeoLocation(location) {
// TODO test this adequately once WD bug is fixed
this.appModel.lat = location.latitude;
this.appModel.long = location.longitude;
-};
+ }
-commands.getGeoLocation = async function getGeoLocation() {
+ async getGeoLocation() {
return this.appModel.currentGeoLocation;
-};
+ }
-commands.getPageSource = async function getPageSource() {
+ async getPageSource() {
return this.appModel.rawXml;
-};
+ }
-commands.getOrientation = async function getOrientation() {
+ async getOrientation() {
return this.appModel.orientation;
-};
+ }
-commands.setOrientation = async function setOrientation(o) {
- if (!_.includes(['LANDSCAPE', 'PORTRAIT'], o)) {
+ /**
+ *
+ * @param {import('../types').FakeDriverCaps['orientation']} o
+ */
+ async setOrientation(o) {
+ if (!ORIENTATIONS.has(o)) {
throw new errors.UnknownError('Orientation must be LANDSCAPE or PORTRAIT');
}
this.appModel.orientation = o;
-};
+ }
-commands.getScreenshot = async function getScreenshot() {
+ async getScreenshot() {
return this.appModel.getScreenshot();
-};
+ }
-commands.getWindowSize = async function getWindowSize() {
+ async getWindowSize() {
return {width: this.appModel.width, height: this.appModel.height};
-};
+ }
-commands.getWindowRect = async function getWindowRect() {
+ async getWindowRect() {
return {width: this.appModel.width, height: this.appModel.height, x: 0, y: 0};
-};
+ }
-commands.performActions = async function performActions(actions) {
+ async performActions(actions) {
this.appModel.actionLog.push(actions);
-};
+ }
-commands.releaseActions = async function releaseActions() {};
+ async releaseActions() {}
-commands.getLog = async function getLog(type) {
+ async getLog(type) {
switch (type) {
case 'actions':
return this.appModel.actionLog;
default:
throw new Error(`Don't understand log type '${type}'`);
}
-};
+ }
-commands.execute = async function execute(script, args) {
+ async execute(script, args) {
return await this.executeMethod(script, args);
-};
+ }
/**
* Add two or maybe even three numbers
*
- * @appiumCommand
* @param {number} num1
* @param {number} num2
- * @param {?number} num3
+ * @param {number} [num3]
* @returns {Promise<number>}
*/
-commands.fakeAddition = async function fakeAddition(num1, num2, num3) {
+ async fakeAddition(num1, num2, num3 = 0) {
return num1 + num2 + (num3 ?? 0);
-};
+ }
+ }
+
+ return GeneralCommands;
+}
-Object.assign(extensions, commands, helpers);
-export {commands, helpers};
-export default extensions;
+/**
+ * @typedef {import('../driver').FakeDriverCore} FakeDriverCore
+ */
+
+/**
+ * @template T,[U={}],[V=Array<any>]
+ * @typedef {import('@appium/types').Class<T,U,V>} Class
+ */
+
+/**
+ * @typedef {import('../types').IGeneralCommands} IGeneralCommands
+ */

MODIFY
packages/fake-driver/lib/commands/index.js
packages/fake-driver/lib/commands/index.js
-import contextCommands from './contexts';
-import findCommands from './find';
-import elementCommands from './element';
-import generalCommands from './general';
-import alertCommands from './alert';
+import {AlertMixin} from './alert';
+import {ContextsMixin} from './contexts';
+import {ElementMixin} from './element';
+import {FindMixin} from './find';
+import {GeneralMixin} from './general';
-let commands = {};
-
-Object.assign(
- commands,
- contextCommands,
- findCommands,
- elementCommands,
- generalCommands,
- alertCommands
-);
-
-export default commands;
+/**
+ *
+ * @param {import('@appium/types').Class<import('../driver').FakeDriverCore>} Base
+ * @returns
+ */
+export function FakeDriverMixin(Base) {
+ return GeneralMixin(FindMixin(ElementMixin(AlertMixin(ContextsMixin(Base)))));
+}
/* // TODO:
//rest.post('/wd/hub/session/:sessionId?/touch/click', controller.doClick);

MODIFY
packages/fake-driver/lib/driver.js
packages/fake-driver/lib/driver.js
-// @ts-check
-
import B from 'bluebird';
import {BaseDriver, errors} from 'appium/driver';
import {FakeApp} from './fake-app';
-import commands from './commands';
+import {FakeDriverMixin} from './commands';
-const CONSTRAINTS = /** @type {const} */ ({
+const FAKE_DRIVER_CONSTRAINTS = /** @type {const} */ ({
app: {
presence: true,
isString: true,
@@ -16,20 +14,29 @@ const CONSTRAINTS = /** @type {const} */ ({
});
/**
+ * @typedef {typeof FAKE_DRIVER_CONSTRAINTS} FakeDriverConstraints
+ */
+
+/**
+ * @extends {BaseDriver<FakeDriverConstraints>}
+ * @implements {ExternalDriver<FakeDriverConstraints>}
*/
-class FakeDriver extends BaseDriver {
+export class FakeDriverCore extends BaseDriver {
+ desiredCapConstraints = FAKE_DRIVER_CONSTRAINTS;
+
+ /** @type {string} */
+ curContext;
+
+ appModel = new FakeApp();
+
constructor(opts = {}, shouldValidateCaps = true) {
super(opts, shouldValidateCaps);
- this.appModel = null;
this.curContext = 'NATIVE_APP';
this.elMap = {};
this.focusedElId = null;
this.maxElId = 0;
this.fakeThing = null;
- this.cliArgs = {};
this._proxyActive = false;
-
- this.desiredCapConstraints = CONSTRAINTS;
}
proxyActive() {
@@ -49,17 +56,21 @@ class FakeDriver extends BaseDriver {
res.status(200).send(JSON.stringify(resBodyObj));
}
- proxyCommand(/*url, method, body*/) {
- return 'proxied via proxyCommand';
+ /**
+ * @template [T=any]
+ * @returns {Promise<T>}
+ */
+ async proxyCommand(/*url, method, body*/) {
+ return /** @type {T} */ (/** @type {unknown} */ ('proxied via proxyCommand'));
}
/**
*
- * @param {W3CCapabilities} jsonwpDesiredCapabilities
- * @param {W3CCapabilities} [jsonwpRequiredCaps]
- * @param {W3CCapabilities} [w3cCapabilities]
+ * @param {W3CFakeDriverCaps} jsonwpDesiredCapabilities
+ * @param {W3CFakeDriverCaps} [jsonwpRequiredCaps]
+ * @param {W3CFakeDriverCaps} [w3cCapabilities]
* @param {import('@appium/types').DriverData[]} [otherSessionData]
- * @returns {Promise<[string, any]>}
+ * @returns {Promise<[string,FakeDriverCaps]>}
*/
async createSession(
jsonwpDesiredCapabilities,
@@ -81,8 +92,7 @@ class FakeDriver extends BaseDriver {
}
}
- let [sessionId, caps] =
- /** @type {[string, import('@appium/types').DriverCaps<FakeDriver>]} */ (
+ let [sessionId, caps] = /** @type {[string, FakeDriverCaps]} */ (
await super.createSession(
jsonwpDesiredCapabilities,
jsonwpRequiredCaps,
@@ -90,7 +100,6 @@ class FakeDriver extends BaseDriver {
otherSessionData
)
);
- this.appModel = new FakeApp();
this.caps = caps;
await this.appModel.loadApp(caps.app);
return [sessionId, caps];
@@ -110,7 +119,6 @@ class FakeDriver extends BaseDriver {
/**
* Set the 'thing' value (so that it can be retrieved later)
*
- * @appiumCommand
* @param {any} thing
* @returns {Promise<null>}
*/
@@ -123,8 +131,7 @@ class FakeDriver extends BaseDriver {
/**
* Get the driver args that were sent in via the CLI
*
- * @appiumCommand
- * @returns {Promise<Record<string, any>>}
+ * @returns {Promise<typeof this.cliArgs>}
*/
async getFakeDriverArgs() {
await B.delay(1);
@@ -134,6 +141,9 @@ class FakeDriver extends BaseDriver {
static newMethodMap = /** @type {const} */ ({
'/session/:sessionId/fakedriver': {
GET: {command: 'getFakeThing'},
+ /**
+ * Sets a fake thing
+ */
POST: {command: 'setFakeThing', payloadParams: {required: ['thing']}},
},
'/session/:sessionId/fakedriverargs': {
@@ -146,6 +156,9 @@ class FakeDriver extends BaseDriver {
command: 'fakeAddition',
params: {required: ['num1', 'num2'], optional: ['num3']},
},
+ /**
+ * Gets a thing (a fake thing)
+ */
'fake: getThing': {
command: 'getFakeThing',
},
@@ -155,8 +168,18 @@ class FakeDriver extends BaseDriver {
},
});
+ /**
+ * Add two or maybe even three numbers
+ *
+ * @param {number} num1
+ * @param {number} num2
+ * @param {number} [num3]
+ * @returns {Promise<number>}
+ */
// eslint-disable-next-line no-unused-vars
- async fakeAddition(num1, num2, num3 = 0) {}
+ async fakeAddition(num1, num2, num3 = 0) {
+ throw new errors.NotImplementedError();
+ }
static fakeRoute(req, res) {
res.send(JSON.stringify({fakedriver: 'fakeResponse'}));
@@ -164,23 +187,30 @@ class FakeDriver extends BaseDriver {
static async updateServer(expressApp, httpServer, cliArgs) {
// eslint-disable-line require-await
- expressApp.all('/fakedriver', FakeDriver.fakeRoute);
+ expressApp.all('/fakedriver', FakeDriverCore.fakeRoute);
expressApp.all('/fakedriverCliArgs', (req, res) => {
res.send(JSON.stringify(cliArgs));
});
}
}
-Object.assign(FakeDriver.prototype, commands);
-
-export {FakeDriver};
+/**
+ * @extends FakeDriverCore
+ */
+export class FakeDriver extends FakeDriverMixin(FakeDriverCore) {}
+export default FakeDriver;
/**
- * @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
- * @typedef {import('@appium/types').ExternalDriver} ExternalDriver
+ * @typedef {import('./types').FakeDriverCaps} FakeDriverCaps
+ * @typedef {import('./types').W3CFakeDriverCaps} W3CFakeDriverCaps
*/
/**
* @template {import('@appium/types').Driver} D
* @typedef {import('@appium/types').DriverClass<D>} DriverClass
*/
+
+/**
+ * @template {import('@appium/types').Constraints} C
+ * @typedef {import('@appium/types').ExternalDriver<C>} ExternalDriver
+ */

MODIFY
packages/fake-driver/lib/fake-app.js
packages/fake-driver/lib/fake-app.js
@@ -29,9 +29,13 @@ class FakeApp {
if (nodes.length < 1) {
throw new Error('No title!');
}
- return nodes[0].firstChild.data;
+ const node = /** @type {Node} */ (nodes[0]);
+ return /** @type {ElementWithData} */ (node.firstChild).data;
}
+ /**
+ * @type {import('@appium/types').Location}
+ */
get currentGeoLocation() {
return {
latitude: this.lat,
@@ -47,18 +51,24 @@ class FakeApp {
this.currentOrientation = o;
}
+ /**
+ * @type {number}
+ */
get width() {
if (this._width === null) {
this.setDims();
}
- return this._width;
+ return /** @type {number} */ (this._width);
}
+ /**
+ * @type {number}
+ */
get height() {
if (this._width === null) {
this.setDims();
}
- return this._width;
+ return /** @type {number} */ (this._width);
}
setDims() {
@@ -133,10 +143,10 @@ class FakeApp {
}
setAlertText(text) {
- if (!this.activeAlert.hasPrompt()) {
+ if (!this.activeAlert?.hasPrompt()) {
throw new Error('No prompt to set text of');
}
- this.activeAlert.setAttr('prompt', text);
+ this.activeAlert?.setAttr('prompt', text);
}
showAlert(alertId) {
@@ -148,7 +158,7 @@ class FakeApp {
}
alertText() {
- return this.activeAlert.getAttr('prompt') || this.activeAlert.nodeAttrs.text;
+ return this.activeAlert?.getAttr('prompt') || this.activeAlert?.nodeAttrs.text;
}
handleAlert() {
@@ -167,3 +177,7 @@ class FakeWebView {
}
export {FakeApp};
+
+/**
+ * @typedef {Element & {data: any}} ElementWithData
+ */

MODIFY
packages/fake-driver/lib/index.js
packages/fake-driver/lib/index.js
@@ -5,11 +5,11 @@ const DEFAULT_HOST = 'localhost';
const DEFAULT_PORT = 4774;
async function main() {
- const getArgValue = (argName) => {
+ const getArgValue = (/** @type {string} */ argName) => {
const argIndex = process.argv.indexOf(argName);
return argIndex > 0 ? process.argv[argIndex + 1] : null;
};
- const port = parseInt(getArgValue('--port'), 10) || DEFAULT_PORT;
+ const port = parseInt(String(getArgValue('--port')), 10) || DEFAULT_PORT;
const host = getArgValue('--host') || DEFAULT_HOST;
return await startServer(port, host);
}

ADD
packages/fake-driver/lib/types.ts
+import type {DriverCaps, ExternalDriver, W3CDriverCaps} from '@appium/types';
+import type {FakeDriverConstraints} from './driver';
+import {FakeApp} from './fake-app';
+import {FakeElement} from './fake-element';
+
+/**
+ * W3C-style caps for {@link FakeDriver}
+ * @public
+ */
+export type W3CFakeDriverCaps = W3CDriverCaps<FakeDriverConstraints>;
+
+/**
+ * Capabilities for {@link FakeDriver}
+ * @public
+ */
+export type FakeDriverCaps = DriverCaps<FakeDriverConstraints>;
+
+export interface IFakeDriver extends ExternalDriver<FakeDriverConstraints> {
+ elMap: Record<string, FakeElement>;
+ maxElId: number;
+ appModel: FakeApp;
+ focusedElId: string;
+ curContext: string;
+}
+
+export interface IGeneralCommands extends IFakeDriver {
+ fakeAddition(num1: number, num2: number, num3?: number): Promise<number>;
+}
+
+export interface IContextsCommands extends IFakeDriver {
+ assertWebviewContext(): void;
+}
+
+export interface IElementCommands extends IFakeDriver {
+ getElement(id: string): FakeElement;
+ getElements(ids: string[]): FakeElement[];
+ setValue(text: string, id: string): Promise<void>;
+}
+
+export interface IAlertCommands extends IFakeDriver {
+ assertNoAlert(): void;
+}

MODIFY
packages/fake-driver/tsconfig.json
packages/fake-driver/tsconfig.json
"extends": "../../config/tsconfig.base.json",
"compilerOptions": {
"outDir": "build",
+ "checkJs": true,
"paths": {
"@appium/types": ["../types"],
"appium/support": ["../appium/support"],
"@appium/driver-test-support": ["../driver-test-support"]
}
},
- "include": ["./lib"],
+ "include": ["lib"],
"references": [
{"path": "../types"},
{"path": "../support"},
- {"path": "../driver-test-support"}
+ {"path": "../driver-test-support"},
+ {"path": "../base-driver"}
]
}

chore(fake-driver): use "mixin" style class composition; many type fixes