chore(fake-driver): annihilate loathsome mixin classes
I did here what I did to `appium-xcuitest-driver`, which was to assign the command methods to the class as fields.  Easier this way.
MODIFY
packages/fake-driver/lib/commands/alert.js
packages/fake-driver/lib/commands/alert.js
import {errors} from 'appium/driver';
+export default {
/**
- * @template {Class<import('../types').IContextsCommands & import('@appium/types').IFindCommands>} T
- * @param {T} Base
- * @returns {Class<AlertCommands>}
+ * @this {FakeDriver}
*/
-export function AlertMixin(Base) {
- /**
- * @implements {IAlertCommands}
- */
- class AlertCommands extends Base {
assertNoAlert() {
if (this.appModel.hasAlert()) {
throw new errors.UnexpectedAlertOpenError();
}
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
assertAlert() {
if (!this.appModel.hasAlert()) {
throw new errors.NoAlertOpenError();
}
- }
+ },
/**
* Get the text of an alert
*
* @returns {Promise<string>}
+ * @this {FakeDriver}
*/
async getAlertText() {
this.assertAlert();
return this.appModel.alertText();
- }
+ },
/**
* Set the text of an alert
*
* @param {string} text
* @returns {Promise<void>}
+ * @this {FakeDriver}
*/
async setAlertText(text) {
this.assertAlert();
@@ -45,38 +44,30 @@ export function AlertMixin(Base) {
} catch (e) {
throw new errors.InvalidElementStateError();
}
- }
+ },
/**
* Accept an alert
*
* @returns {Promise<void>}
+ * @this {FakeDriver}
*/
async postAcceptAlert() {
this.assertAlert();
this.appModel.handleAlert();
- }
+ },
/**
* Dismiss an alert
*
- * @returns {Promise<null>}
- */
- postDismissAlert = this.postAcceptAlert;
- }
-
- return AlertCommands;
-}
-
-/**
- * @typedef {import('../driver').FakeDriverCore} FakeDriverCore
- */
-
-/**
- * @template T,[U={}],[V=Array<any>]
- * @typedef {import('@appium/types').Class<T,U,V>} Class
+ * @returns {Promise<void>}
+ * @this {FakeDriver}
*/
+ async postDismissAlert() {
+ return this.postAcceptAlert();
+ },
+};
/**
- * @typedef {import('../types').IAlertCommands} IAlertCommands
+ * @typedef {import('../driver').FakeDriver} FakeDriver
*/

MODIFY
packages/fake-driver/lib/commands/contexts.js
packages/fake-driver/lib/commands/contexts.js
import _ from 'lodash';
import {errors} from 'appium/driver';
+export default {
/**
- *
- * @template {Class<import('../types').IFakeDriver>} T
- * @param {T} Base
- */
-export function ContextsMixin(Base) {
- /**
- * @implements {IContextsCommands}
+ * @this {FakeDriver}
*/
- class ContextsCommands extends Base {
getRawContexts() {
let contexts = {NATIVE_APP: null, PROXY: null};
let wvs = this.appModel?.getWebviews() ?? [];
@@ -18,36 +12,41 @@ export function ContextsMixin(Base) {
contexts[`WEBVIEW_${i}`] = wvs[i - 1];
}
return contexts;
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
assertWebviewContext() {
if (this.curContext === 'NATIVE_APP') {
throw new errors.InvalidContextError();
}
- }
+ },
- // do not add a description to this method's docstring
- /*
+ /**
* @returns {Promise<string>}
+ * @this {FakeDriver}
*/
async getCurrentContext() {
return this.curContext;
- }
+ },
/**
* Get the list of available contexts
*
* @returns {Promise<string[]>}
+ * @this {FakeDriver}
*/
async getContexts() {
return _.keys(this.getRawContexts());
- }
+ },
/**
* Set the current context
*
* @param {string} context - name of the context
* @returns {Promise<void>}
+ * @this {FakeDriver}
*/
async setContext(context) {
let contexts = this.getRawContexts();
@@ -65,13 +64,14 @@ export function ContextsMixin(Base) {
} else {
throw new errors.NoSuchContextError();
}
- }
+ },
/**
* Set the active frame
*
* @param {number} frameId
* @returns {Promise<void>}
+ * @this {FakeDriver}
*/
async setFrame(frameId) {
this.assertWebviewContext();
@@ -84,18 +84,9 @@ export function ContextsMixin(Base) {
}
this.appModel.activateFrame(nodes[0]);
}
- }
- }
-
- return ContextsCommands;
-}
-
-/**
- * @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
+ * @typedef {import('../driver').FakeDriver} FakeDriver
*/

MODIFY
packages/fake-driver/lib/commands/element.js
packages/fake-driver/lib/commands/element.js
import _ from 'lodash';
import {errors} from 'appium/driver';
-
-/**
- * @template {Class<import('../types').IContextsCommands & import('../types').IAlertCommands>} T
- * @param {T} Base
- */
-export function ElementMixin(Base) {
+export default {
/**
- * @implements {IElementCommands}
+ * @this {FakeDriver}
*/
- class ElementCommands extends Base {
getElements(elIds) {
for (let elId of elIds) {
if (!_.has(this.elMap, elId)) {
@@ -17,32 +11,50 @@ export function ElementMixin(Base) {
}
}
return elIds.map((e) => this.elMap[e]);
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
getElement(elId) {
return this.getElements([elId])[0];
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async getName(elementId) {
let el = this.getElement(elementId);
return el.tagName;
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async elementDisplayed(elementId) {
let el = this.getElement(elementId);
return el.isVisible();
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async elementEnabled(elementId) {
let el = this.getElement(elementId);
return el.isEnabled();
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async elementSelected(elementId) {
let el = this.getElement(elementId);
return el.isSelected();
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async setValue(keys, elementId) {
let value = keys;
if (keys instanceof Array) {
@@ -53,20 +65,27 @@ export function ElementMixin(Base) {
throw new errors.InvalidElementStateError();
}
el.setAttr('value', value);
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async getText(elementId) {
let el = this.getElement(elementId);
return el.getAttr('value');
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async clear(elementId) {
await this.setValue('', elementId);
- }
+ },
/**
* This comment should be displayed instead of the one from ExternalDriver
* @param {string} elementId
+ * @this {FakeDriver}
*/
async click(elementId) {
this.assertNoAlert();
@@ -76,45 +95,67 @@ export function ElementMixin(Base) {
}
el.click();
this.focusedElId = elementId;
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async getAttribute(attr, elementId) {
let el = this.getElement(elementId);
return el.getAttr(attr);
- }
+ },
+
+ /**
+ * @this {FakeDriver}
+ */
getElementRect(elementId) {
let el = this.getElement(elementId);
return el.getElementRect();
- }
+ },
+
+ /**
+ * @this {FakeDriver}
+ */
getSize(elementId) {
let el = this.getElement(elementId);
return el.getSize();
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
equalsElement(el1Id, el2Id) {
let el1 = this.getElement(el1Id);
let el2 = this.getElement(el2Id);
return el1.equals(el2);
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async getCssProperty(prop, elementId) {
this.assertWebviewContext();
let el = this.getElement(elementId);
return el.getCss(prop);
- }
+ },
- getLocationInView = this.getLocation;
- }
-
- return ElementCommands;
-}
+ /**
+ * @param {string} elementId
+ * @this {FakeDriver}
+ */
+ async getLocation(elementId) {
+ const el = this.getElement(elementId);
+ return el.getLocation();
+ },
/**
- * @typedef {import('../driver').FakeDriverCore} FakeDriverCore
- * @typedef {import('../types').IElementCommands} IElementCommands
+ * @this {FakeDriver}
*/
+ async getLocationInView(elementId) {
+ return this.getLocation(elementId);
+ },
+};
/**
- * @template T,[U={}],[V=Array<any>]
- * @typedef {import('@appium/types').Class<T,U,V>} Class
+ * @typedef {import('../driver').FakeDriver} FakeDriver
*/

MODIFY
packages/fake-driver/lib/commands/find.js
packages/fake-driver/lib/commands/find.js
@@ -2,15 +2,10 @@ import _ from 'lodash';
import {errors} from 'appium/driver';
import {FakeElement} from '../fake-element';
+export default {
/**
- * @template {Class<import('../types').IElementCommands>} T
- * @param {T} Base
+ * @this {FakeDriver}
*/
-export function FindMixin(Base) {
- /**
- * @implements {IFindCommands}
- */
- class FindCommands extends Base {
getExistingElementForNode(node) {
for (let [id, el] of _.toPairs(this.elMap)) {
if (el.node === node) {
@@ -18,8 +13,11 @@ export function FindMixin(Base) {
}
}
return null;
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
wrapNewEl(obj) {
// first check and see if we already have a ref to this element
let existingElId = this.getExistingElementForNode(obj);
@@ -31,7 +29,8 @@ export function FindMixin(Base) {
this.maxElId++;
this.elMap[this.maxElId.toString()] = new FakeElement(obj, this.appModel);
return {ELEMENT: this.maxElId.toString()};
- }
+ },
+
/**
* @template {boolean} Mult
* @template [Ctx=any]
@@ -40,6 +39,7 @@ export function FindMixin(Base) {
* @param {Mult} mult
* @param {Ctx} [context]
* @returns {Promise<Mult extends true ? Element[] : Element>}
+ * @this {FakeDriver}
*/
async findElOrEls(strategy, selector, mult, context) {
let qMap = {
@@ -76,47 +76,43 @@ export function FindMixin(Base) {
throw new errors.NoSuchElementError();
}
return /** @type {Mult extends true ? Element[] : Element} */ (retval);
- }
+ },
/**
* This should override whatever's in ExternalDriver
* @param {string} strategy Strategy
* @param {string} selector Selector
+ * @this {FakeDriver}
*/
async findElement(strategy, selector) {
return this.findElOrEls(strategy, selector, false);
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async findElements(strategy, selector) {
return this.findElOrEls(strategy, selector, true);
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
async findElementFromElement(strategy, selector, elementId) {
let el = this.getElement(elementId);
return this.findElOrEls(strategy, selector, false, el.xmlFragment);
- }
+ },
+ /**
+ * @this {FakeDriver}
+ */
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
- */
-
-/**
- * @template [Ctx=any]
- * @typedef {import('@appium/types').IFindCommands<Ctx>} IFindCommands
+ * @typedef {import('../driver').FakeDriver} FakeDriver
*/

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

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

MODIFY
packages/fake-driver/lib/driver.js
packages/fake-driver/lib/driver.js
@@ -2,7 +2,7 @@ import B from 'bluebird';
import {BaseDriver, errors} from 'appium/driver';
import {deprecatedCommandsLogged} from '@appium/base-driver/build/lib/protocol/protocol';
import {FakeApp} from './fake-app';
-import {FakeDriverMixin} from './commands';
+import {alert, contexts, element, find, general} from './commands';
const FAKE_DRIVER_CONSTRAINTS = /** @type {const} */ ({
app: {
@@ -22,7 +22,7 @@ const FAKE_DRIVER_CONSTRAINTS = /** @type {const} */ ({
* @extends {BaseDriver<FakeDriverConstraints>}
* @implements {ExternalDriver<FakeDriverConstraints>}
*/
-export class FakeDriverCore extends BaseDriver {
+export class FakeDriver extends BaseDriver {
desiredCapConstraints = FAKE_DRIVER_CONSTRAINTS;
/** @type {string} */
@@ -190,36 +190,92 @@ export class FakeDriverCore 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) {
- throw new errors.NotImplementedError();
- }
-
static fakeRoute(req, res) {
res.send(JSON.stringify({fakedriver: 'fakeResponse'}));
}
static async updateServer(expressApp, httpServer, cliArgs) {
// eslint-disable-line require-await
- expressApp.all('/fakedriver', FakeDriverCore.fakeRoute);
+ expressApp.all('/fakedriver', FakeDriver.fakeRoute);
expressApp.all('/fakedriverCliArgs', (req, res) => {
res.send(JSON.stringify(cliArgs));
});
}
+
+ /*********
+ * ALERT *
+ *********/
+ assertNoAlert = alert.assertNoAlert;
+ assertAlert = alert.assertAlert;
+ getAlertText = alert.getAlertText;
+ setAlertText = alert.setAlertText;
+ postAcceptAlert = alert.postAcceptAlert;
+ postDismissAlert = alert.postDismissAlert;
+
+ /************
+ * CONTEXTS *
+ ************/
+ getRawContexts = contexts.getRawContexts;
+ assertWebviewContext = contexts.assertWebviewContext;
+ getCurrentContext = contexts.getCurrentContext;
+ getContexts = contexts.getContexts;
+ setContext = contexts.setContext;
+ setFrame = contexts.setFrame;
+
+ /************
+ * ELEMENTS *
+ ************/
+ getElements = element.getElements;
+ getElement = element.getElement;
+ getName = element.getName;
+ elementDisplayed = element.elementDisplayed;
+ elementEnabled = element.elementEnabled;
+ elementSelected = element.elementSelected;
+ setValue = element.setValue;
+ getText = element.getText;
+ clear = element.clear;
+ click = element.click;
+ getAttribute = element.getAttribute;
+ getElementRect = element.getElementRect;
+ getSize = element.getSize;
+ equalsElement = element.equalsElement;
+ getCssProperty = element.getCssProperty;
+ getLocation = element.getLocation;
+ getLocationInView = element.getLocationInView;
+
+ /********
+ * FIND *
+ ********/
+ getExistingElementForNode = find.getExistingElementForNode;
+ wrapNewEl = find.wrapNewEl;
+ findElOrEls = find.findElOrEls;
+ findElement = find.findElement;
+ findElements = find.findElements;
+ findElementFromElement = find.findElementFromElement;
+ findElementsFromElement = find.findElementsFromElement;
+
+ /***********
+ * GENERAL *
+ ***********/
+ title = general.title;
+ keys = general.keys;
+ setGeoLocation = general.setGeoLocation;
+ getGeoLocation = general.getGeoLocation;
+ getPageSource = general.getPageSource;
+ getOrientation = general.getOrientation;
+ setOrientation = general.setOrientation;
+ getScreenshot = general.getScreenshot;
+ getWindowSize = general.getWindowSize;
+ getWindowRect = general.getWindowRect;
+ performActions = general.performActions;
+ getLog = general.getLog;
+ mobileShake = general.mobileShake;
+ doubleClick = general.doubleClick;
+ execute = general.execute;
+ fakeAddition = general.fakeAddition;
+ releaseActions = general.releaseActions;
}
-/**
- * @extends FakeDriverCore
- */
-export class FakeDriver extends FakeDriverMixin(FakeDriverCore) {}
export default FakeDriver;
/**

MODIFY
packages/fake-driver/lib/types.ts
packages/fake-driver/lib/types.ts
-import type {DriverCaps, ExternalDriver, W3CDriverCaps} from '@appium/types';
+import type {DriverCaps, 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}
@@ -14,29 +12,3 @@ export type W3CFakeDriverCaps = W3CDriverCaps<FakeDriverConstraints>;
* @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/test/unit/command.spec.js
packages/fake-driver/test/unit/command.spec.js
import _ from 'lodash';
-import contextCommands from '../../lib/commands/contexts';
-import elementCommands from '../../lib/commands/element';
-import findCommands from '../../lib/commands/find';
-import generalCommands from '../../lib/commands/general';
-import exportedCommands from '../../lib/commands';
+import * as commands from '../../lib/commands';
+import {FakeDriver} from '../../lib/driver';
describe('Driver commands', function () {
- let allCommands = [
- _.keys(contextCommands),
- _.keys(elementCommands),
- _.keys(findCommands),
- _.keys(generalCommands),
- ];
- let totalCommands = _.sum(allCommands.map((c) => c.length));
- it('should not overlap between files', function () {
- _.union(...allCommands).length.should.equal(totalCommands);
- });
- it('should export all commands and not leave any out', function () {
- _.difference(_.union(...allCommands), _.keys(exportedCommands)).should.eql([]);
+ const allCommands = Object.values(commands).map(Object.keys).flat();
+
+ it('should import all commands and not leave any out', function () {
+ const driver = new FakeDriver();
+ _.difference(allCommands, Object.keys(driver)).should.be.empty;
});
});

chore(fake-driver): annihilate loathsome mixin classes

I did here what I did to `appium-xcuitest-driver`, which was to assign the command methods to the class as fields.  Easier this way.