chore(base-driver,types): implement better types for custom log formats

Previously, the value of any `Driver#supportedLogTypes` prop would ultimately be unaware of what format the log data is actually stored in (and thus, what the `GET /session/:sessionId/log` cmd responds with).  Now this can be declared by using the `LogDef<C extends Constraints, LogEntry = string>` type.

Example:

```js
  supportedLogTypes = {
    debug: /** @type {import('@appium/types').LogDef<RokuDriverCapConstraints, string>} */ ({
      description: 'Roku debug logs',
      /** @param {RokuDriver} driver */
      async getter(driver) {
        return await ['foo'];
      },
    }),
  };
```

This is a log named `debug` which stores strings.  The `getter` function is always expected to return a type of `LogEntry[]` and receives a type of `Driver<C>`. In this case, it's the default log entry type (`string`) and `RokuDriver`, which implements `Driver<RokuDriverCapConstraints>` where `RokuDriverCapConstraints` extends `Constraints`.

It was kind of tricky since `supportedLogTypes` is an instance field and we don't have access to `this`, so cannot derive the type of the `driver` parameter from it, nor can we use `this` in the `ILogCommands` interface.

Renamed these various mixin interfaces to be prefixed with `I`. All of the mixins themselves (the implementations) are now driver-aware via the `C` type parameter and thus can reference driver-specific caps or options, if needed.
This commit is contained in:
Christopher Hiller
2022-10-21 14:03:10 -07:00
parent fe4c6d68a0
commit 0bd4e40b38
10 changed files with 172 additions and 99 deletions

View File

@@ -3,8 +3,9 @@
import _ from 'lodash';
/**
* @param {TimeoutBase} Base
* @returns {EventBase}
* @template {Constraints} C
* @param {import('./timeout').TimeoutBase<C>} Base
* @returns {EventBase<C>}
*/
export function EventMixin(Base) {
/**
@@ -52,7 +53,11 @@ export function EventMixin(Base) {
}
/**
* @typedef {import('@appium/types').EventCommands} IEventCommands
* @typedef {import('./timeout').TimeoutBase} TimeoutBase
* @typedef {import('../driver').BaseDriverBase<import('@appium/types').TimeoutCommands & IEventCommands>} EventBase
* @typedef {import('@appium/types').Constraints} Constraints
* @typedef {import('@appium/types').IEventCommands} IEventCommands
*/
/**
* @template {Constraints} C
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & IEventCommands>} EventBase
*/

View File

@@ -2,8 +2,9 @@ import _ from 'lodash';
import {errors, makeArgs, checkParams} from '../../protocol';
/**
* @param {SessionBase} Base
* @returns {ExecuteBase}
* @template {Constraints} C
* @param {import('./session').SessionBase<C>} Base
* @returns {ExecuteBase<C>}
*/
export function ExecuteMixin(Base) {
/**
@@ -57,9 +58,13 @@ export function ExecuteMixin(Base) {
}
/**
* @typedef {import('@appium/types').ExecuteCommands} IExecuteCommands
* @typedef {import('@appium/types').IExecuteCommands} IExecuteCommands
* @typedef {import('@appium/types').Driver} Driver
* @typedef {import('@appium/types').DriverClass} DriverClass
* @typedef {import('./session').SessionBase} SessionBase
* @typedef {import('../driver').BaseDriverBase<import('@appium/types').TimeoutCommands & import('@appium/types').EventCommands & import('@appium/types').FindCommands & import('@appium/types').LogCommands & import('@appium/types').SettingsCommands & import('@appium/types').SessionCommands & IExecuteCommands>} ExecuteBase
* @typedef {import('@appium/types').Constraints} Constraints
*/
/**
* @template {Constraints} C
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & import('@appium/types').IEventCommands & import('@appium/types').IFindCommands & import('@appium/types').ILogCommands<C> & import('@appium/types').ISettingsCommands & import('@appium/types').SessionCommands & IExecuteCommands>} ExecuteBase
*/

View File

@@ -4,9 +4,9 @@
import {errors} from '../../protocol';
/**
*
* @param {EventBase} Base
* @returns {FindBase}
* @template {Constraints} C
* @param {import('./event').EventBase<C>} Base
* @returns {FindBase<C>}
*/
export function FindMixin(Base) {
/**
@@ -96,7 +96,12 @@ export function FindMixin(Base) {
/**
* @typedef {import('@appium/types').Element} Element
* @typedef {import('@appium/types').FindCommands} IFindCommands
* @typedef {import('./event').EventBase} EventBase
* @typedef {import('../driver').BaseDriverBase<import('@appium/types').TimeoutCommands & import('@appium/types').EventCommands & IFindCommands>} FindBase
* @typedef {import('@appium/types').Constraints} Constraints
* @typedef {import('@appium/types').IFindCommands} IFindCommands
* @typedef {import('@appium/types').ITimeoutCommands} ITimeoutCommands
* @typedef {import('@appium/types').IEventCommands} IEventCommands
*/
/**
* @template {Constraints} C
* @typedef {import('../driver').BaseDriverBase<C, ITimeoutCommands & IEventCommands & IFindCommands>} FindBase
*/

View File

@@ -1,5 +1,5 @@
// @ts-check
import _ from 'lodash';
import {EventMixin} from './event';
import {FindMixin} from './find';
import {LogMixin} from './log';
@@ -9,22 +9,27 @@ import {TimeoutMixin} from './timeout';
import {ExecuteMixin} from './execute';
/**
* Applies all the mixins to the `BaseDriverBase` class.
* Returns a `BaseDriver` class.
* @param {BaseDriverBase} Base
* Applies all the mixins to the `BaseDriverBase` class; returns a `BaseDriver` class definition.
* Each mixin is applied in the order it is listed here, and each type is a union with the previous.
*
* @template {Constraints} C
* @param {BaseDriverBase<C>} Base
*/
export function createBaseDriverClass(Base) {
const WithTimeoutCommands = TimeoutMixin(Base);
const WithEventCommands = EventMixin(WithTimeoutCommands);
const WithFindCommands = FindMixin(WithEventCommands);
const WithLogCommands = LogMixin(WithFindCommands);
const WithSettingsCommands = SettingsMixin(WithLogCommands);
const WithSessionCommands = SessionMixin(WithSettingsCommands);
const WithExecuteCommands = ExecuteMixin(WithSessionCommands);
return WithExecuteCommands;
}
export const createBaseDriverClass = _.flow(
TimeoutMixin,
EventMixin,
FindMixin,
LogMixin,
SettingsMixin,
SessionMixin,
ExecuteMixin
);
/**
* @template [T={}]
* @typedef {import('../driver').BaseDriverBase<T>} BaseDriverBase
* @template {Constraints} C
* @typedef {import('../driver').BaseDriverBase<C>} BaseDriverBase
*/
/**
* @typedef {import('@appium/types').Constraints} Constraints
*/

View File

@@ -4,37 +4,40 @@
import _ from 'lodash';
/**
*
* @param {FindBase} Base
* @returns {LogBase}
* @template {Constraints} C
* @param {import('./find').FindBase<C>} Base
* @returns {LogBase<C>}
*/
export function LogMixin(Base) {
/**
* @implements {ILogCommands}
* @implements {ILogCommands<C>}
*/
class LogCommands extends Base {
/** @type {Readonly<import('@appium/types').LogDefRecord<C>>} */
supportedLogTypes;
constructor(...args) {
super(...args);
/** @type {Record<string, LogType<Driver>>} */
this.supportedLogTypes = this.supportedLogTypes ?? {};
this.supportedLogTypes ??= {};
}
async getLogTypes() {
this.log.debug('Retrieving supported log types');
return _.keys(this.supportedLogTypes);
return Object.keys(this.supportedLogTypes);
}
/**
* @this {Driver}
* @param {string} logType
* @this {import('@appium/types').Driver<C>}
* @param {keyof typeof this.supportedLogTypes} logType
* @returns {Promise<import('type-fest').AsyncReturnType<typeof this.supportedLogTypes[keyof typeof this.supportedLogTypes]['getter']>>}
*/
async getLog(logType) {
this.log.debug(`Retrieving '${logType}' logs`);
this.log.debug(`Retrieving '${String(logType)}' logs`);
if (!(await this.getLogTypes()).includes(logType)) {
if (!(logType in this.supportedLogTypes)) {
const logsTypesWithDescriptions = _.mapValues(this.supportedLogTypes, 'description');
throw new Error(
`Unsupported log type '${logType}'. ` +
`Unsupported log type '${String(logType)}'. ` +
`Supported types: ${JSON.stringify(logsTypesWithDescriptions)}`
);
}
@@ -46,13 +49,16 @@ export function LogMixin(Base) {
}
/**
* @typedef {import('@appium/types').LogCommands} ILogCommands
* @typedef {import('@appium/types').Driver} Driver
* @typedef {import('./find').FindBase} FindBase
* @typedef {import('../driver').BaseDriverBase<import('@appium/types').TimeoutCommands & import('@appium/types').EventCommands & import('@appium/types').FindCommands & ILogCommands>} LogBase
* @typedef {import('@appium/types').Constraints} Constraints
* @typedef {import('@appium/types').StringRecord} StringRecord
*/
/**
* @template T
* @typedef {import('@appium/types').LogType<T>} LogType
* @template {Constraints} C
* @typedef {import('@appium/types').ILogCommands<C>} ILogCommands
*/
/**
* @template {Constraints} C
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & import('@appium/types').IEventCommands & import('@appium/types').IFindCommands & ILogCommands<C>>} LogBase
*/

View File

@@ -4,8 +4,9 @@
import _ from 'lodash';
/**
* @param {SettingsBase} Base
* @returns {SessionBase}
* @template {Constraints} C
* @param {import('./settings').SettingsBase<C>} Base
* @returns {SessionBase<C>}
*/
export function SessionMixin(Base) {
/**
@@ -46,6 +47,10 @@ export function SessionMixin(Base) {
* @typedef {import('@appium/types').SessionCommands} ISessionCommands
* @typedef {import('@appium/types').SingularSessionData} SingularSessionData
* @typedef {import('@appium/types').MultiSessionData} MultiSessionData
* @typedef {import('./settings').SettingsBase} SettingsBase
* @typedef {import('../driver').BaseDriverBase<import('@appium/types').TimeoutCommands & import('@appium/types').EventCommands & import('@appium/types').FindCommands & import('@appium/types').LogCommands & import('@appium/types').SettingsCommands & ISessionCommands>} SessionBase
* @typedef {import('@appium/types').Constraints} Constraints
*/
/**
* @template {Constraints} C
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & import('@appium/types').IEventCommands & import('@appium/types').IFindCommands & import('@appium/types').ILogCommands<C> & import('@appium/types').ISettingsCommands & ISessionCommands>} SessionBase
*/

View File

@@ -1,9 +1,9 @@
// @ts-check
/**
*
* @param {ReturnType<import('./log').LogMixin>} Base
* @returns {SettingsBase}
* @template {Constraints} C
* @param {import('./log').LogBase<C>} Base
* @returns {SettingsBase<C>}
*/
export function SettingsMixin(Base) {
/**
@@ -29,7 +29,10 @@ export function SettingsMixin(Base) {
}
/**
* @typedef {import('@appium/types').SettingsCommands} ISettingsCommands
* @typedef {import('./log').LogBase} LogBase
* @typedef {import('../driver').BaseDriverBase<import('@appium/types').TimeoutCommands & import('@appium/types').EventCommands & import('@appium/types').FindCommands & import('@appium/types').LogCommands & ISettingsCommands>} SettingsBase
* @typedef {import('@appium/types').Constraints} Constraints
* @typedef {import('@appium/types').ISettingsCommands} ISettingsCommands
*/
/**
* @template {Constraints} C
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & import('@appium/types').IEventCommands & import('@appium/types').IFindCommands & import('@appium/types').ILogCommands<C> & ISettingsCommands>} SettingsBase
*/

View File

@@ -10,8 +10,9 @@ import {errors} from '../../protocol';
const MIN_TIMEOUT = 0;
/**
* @param {import('../driver').BaseDriverBase} Base
* @returns {TimeoutBase}
* @template {Constraints} C
* @param {import('../driver').BaseDriverBase<C>} Base
* @returns {TimeoutBase<C>}
*/
export function TimeoutMixin(Base) {
/**
@@ -157,6 +158,11 @@ export function TimeoutMixin(Base) {
}
/**
* @typedef {import('@appium/types').TimeoutCommands} ITimeoutCommands
* @typedef {import('../driver').BaseDriverBase<ITimeoutCommands>} TimeoutBase
* @typedef {import('@appium/types').ITimeoutCommands} ITimeoutCommands
* @typedef {import('@appium/types').Constraints} Constraints
*/
/**
* @template {Constraints} C
* @typedef {import('../driver').BaseDriverBase<C, ITimeoutCommands>} TimeoutBase
*/

View File

@@ -445,9 +445,10 @@ export default BaseDriver;
/**
* This is used to extend {@linkcode BaseDriverCore} by the mixins and also external drivers.
* @template {Constraints} C
* @template [Proto={}]
* @template [Static={}]
* @typedef {import('@appium/types').Class<BaseDriverCore & Proto,import('@appium/types').DriverStatic & Static>} BaseDriverBase
* @typedef {import('@appium/types').Class<BaseDriverCore<C> & Proto,import('@appium/types').DriverStatic & Static>} BaseDriverBase
*/
/**
@@ -468,13 +469,9 @@ export default BaseDriver;
*/
/**
* @template {Constraints} C
* @typedef {import('@appium/types').ConstraintsToCaps<C>} ConstraintsToCaps
*/
/**
* @template {Constraints} C
* @typedef {import('@appium/types').Driver<C>} Driver
* @template {Constraints} [C=BaseDriverCapConstraints]
* @template {StringRecord} [CArgs=StringRecord]
* @typedef {import('@appium/types').Driver<C, CArgs>} Driver
*/
/**

View File

@@ -14,8 +14,9 @@ import {
Capabilities,
} from '.';
import {ServerArgs} from './config';
import {AsyncReturnType} from 'type-fest';
export interface TimeoutCommands {
export interface ITimeoutCommands {
timeouts(
type: string,
ms: number | string,
@@ -38,7 +39,7 @@ export interface TimeoutCommands {
parseTimeoutArgument(ms: number | string): number;
}
export interface EventCommands {
export interface IEventCommands {
logCustomEvent(vendor: string, event: string): Promise<void>;
getLogEvents(type?: string | string[]): Promise<EventHistory | Record<string, number>>;
}
@@ -48,7 +49,7 @@ export interface SessionCommands {
getSession(): Promise<SingularSessionData>;
}
export interface ExecuteCommands {
export interface IExecuteCommands {
executeMethod(script: string, args: [StringRecord] | []): Promise<any>;
}
@@ -74,7 +75,7 @@ export type SingularSessionData<
Extra extends StringRecord | void = void
> = Capabilities<C, Extra> & {events?: EventHistory; error?: string};
export interface FindCommands {
export interface IFindCommands {
findElement(strategy: string, selector: string): Promise<Element>;
findElements(strategy: string, selector: string): Promise<Element[]>;
findElementFromElement(strategy: string, selector: string, elementId: string): Promise<Element>;
@@ -101,20 +102,60 @@ export interface FindCommands {
getPageSource(): Promise<string>;
}
export interface LogCommands {
supportedLogTypes: Record<string, LogType<Driver>>;
getLogTypes(): Promise<string[]>;
/**
* Log-related functionality of a {@linkcode Driver}. To be used as a mixin
*/
export interface ILogCommands<C extends Constraints> {
/**
* Gets logs
*
* TODO: `logType` should be a key in `supportedLogTypes`, and the return value of this function
* should be the associated `LogType` object's `LogEntry` parameterized type.
* @param logType - Name/key of log type as defined in {@linkcode LogCommands.supportedLogTypes}.
* Definition of the available log types
*/
getLog(logType: string): Promise<any[]>;
supportedLogTypes: Readonly<LogDefRecord<C>>;
/**
* Get available log types as a list of strings
*/
getLogTypes(): Promise<(keyof ILogCommands<C>['supportedLogTypes'])[]>;
/**
* Get the log for a given log type.
*
* @param logType - Name/key of log type as defined in {@linkcode ILogCommands.supportedLogTypes}.
*/
getLog(
logType: keyof ILogCommands<C>['supportedLogTypes']
): Promise<
AsyncReturnType<
ILogCommands<C>['supportedLogTypes'][keyof ILogCommands<C>['supportedLogTypes']]['getter']
>
>;
}
export interface SettingsCommands {
/**
* A record of {@linkcode LogDef} objects, keyed by the log type name.
* Used in {@linkcode ILogCommands.supportedLogTypes}
*/
export type LogDefRecord<C extends Constraints> = Record<string, LogDef<C>>;
/**
* A definition of a log type
*/
export interface LogDef<C extends Constraints, T = unknown> {
/**
* Description of the log type.
*
* The only place this is used is in error messages if the client provides an invalid log type
* via {@linkcode ILogCommands.getLog}.
*/
description: string;
/**
* Returns all the log data for the given type
*
* This implementation *should* drain, truncate or otherwise reset the log buffer.
*/
getter: (driver: Driver<C>) => Promise<T[]>;
}
export interface ISettingsCommands {
updateSettings: (settings: StringRecord) => Promise<void>;
getSettings(): Promise<StringRecord>;
}
@@ -185,11 +226,6 @@ export interface DeviceSettings<T = any> {
getSettings(): Record<string, T>;
}
export interface LogType<TDriver, LogEntry = string> {
description: string;
getter: (driver: TDriver) => Promise<LogEntry[]>;
}
// WebDriver
export interface Rect {
@@ -327,17 +363,17 @@ export interface Core<C extends Constraints = BaseDriverCapConstraints> {
*/
export interface Driver<
C extends Constraints = BaseDriverCapConstraints,
A extends StringRecord = StringRecord
CArgs extends StringRecord = StringRecord
> extends SessionCommands,
LogCommands,
FindCommands,
SettingsCommands,
TimeoutCommands,
EventCommands,
ILogCommands<C>,
IFindCommands,
ISettingsCommands,
ITimeoutCommands,
IEventCommands,
IExecuteCommands,
SessionHandler<[string, any], void, C>,
ExecuteCommands,
Core {
cliArgs?: A;
cliArgs?: CArgs;
// The following methods are implemented by `BaseDriver`.
executeCommand(cmd: string, ...args: any[]): Promise<any>;
startUnexpectedShutdown(err?: Error): Promise<void>;