@appium/base-driver 10.2.2 → 10.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/lib/basedriver/capabilities.d.ts +4 -0
- package/build/lib/basedriver/capabilities.d.ts.map +1 -1
- package/build/lib/basedriver/capabilities.js +4 -0
- package/build/lib/basedriver/capabilities.js.map +1 -1
- package/build/lib/basedriver/commands/execute.js +7 -17
- package/build/lib/basedriver/commands/execute.js.map +1 -1
- package/build/lib/basedriver/core.d.ts +24 -24
- package/build/lib/basedriver/core.d.ts.map +1 -1
- package/build/lib/basedriver/core.js +29 -29
- package/build/lib/basedriver/core.js.map +1 -1
- package/build/lib/basedriver/driver.d.ts.map +1 -1
- package/build/lib/basedriver/driver.js +7 -2
- package/build/lib/basedriver/driver.js.map +1 -1
- package/build/lib/basedriver/extension-core.d.ts +1 -1
- package/build/lib/basedriver/extension-core.d.ts.map +1 -1
- package/build/lib/basedriver/extension-core.js +1 -1
- package/build/lib/basedriver/extension-core.js.map +1 -1
- package/build/lib/basedriver/helpers.d.ts.map +1 -1
- package/build/lib/basedriver/helpers.js +2 -2
- package/build/lib/basedriver/helpers.js.map +1 -1
- package/build/lib/helpers/levenshtein-match.d.ts +27 -0
- package/build/lib/helpers/levenshtein-match.d.ts.map +1 -0
- package/build/lib/helpers/levenshtein-match.js +61 -0
- package/build/lib/helpers/levenshtein-match.js.map +1 -0
- package/build/lib/jsonwp-proxy/protocol-converter.d.ts +1 -1
- package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/protocol-converter.js +3 -3
- package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.d.ts +53 -98
- package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.js +102 -145
- package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
- package/build/lib/protocol/errors.d.ts +45 -45
- package/build/lib/protocol/errors.d.ts.map +1 -1
- package/build/lib/protocol/errors.js +162 -162
- package/build/lib/protocol/errors.js.map +1 -1
- package/build/lib/protocol/protocol.d.ts +35 -0
- package/build/lib/protocol/protocol.d.ts.map +1 -1
- package/build/lib/protocol/protocol.js +105 -77
- package/build/lib/protocol/protocol.js.map +1 -1
- package/build/lib/protocol/routes.d.ts +6 -0
- package/build/lib/protocol/routes.d.ts.map +1 -1
- package/build/lib/protocol/routes.js +6 -0
- package/build/lib/protocol/routes.js.map +1 -1
- package/lib/basedriver/capabilities.ts +4 -0
- package/lib/basedriver/commands/execute.ts +7 -18
- package/lib/basedriver/core.ts +34 -34
- package/lib/basedriver/driver.ts +9 -2
- package/lib/basedriver/extension-core.ts +1 -1
- package/lib/basedriver/helpers.ts +21 -21
- package/lib/helpers/levenshtein-match.ts +74 -0
- package/lib/jsonwp-proxy/protocol-converter.ts +4 -4
- package/lib/jsonwp-proxy/proxy.ts +506 -0
- package/lib/protocol/errors.ts +281 -246
- package/lib/protocol/protocol.ts +121 -93
- package/lib/protocol/routes.ts +6 -0
- package/package.json +10 -10
- package/tsconfig.json +6 -0
- package/lib/jsonwp-proxy/proxy.js +0 -493
|
@@ -8,9 +8,9 @@ import type {
|
|
|
8
8
|
IExecuteCommands,
|
|
9
9
|
StringRecord,
|
|
10
10
|
} from '@appium/types';
|
|
11
|
+
import {rankLevenshteinCandidates} from '../../helpers/levenshtein-match';
|
|
11
12
|
import {mixin} from './mixin';
|
|
12
13
|
import type {BaseDriver} from '../driver';
|
|
13
|
-
import {distance} from 'fastest-levenshtein';
|
|
14
14
|
|
|
15
15
|
declare module '../driver' {
|
|
16
16
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -34,30 +34,19 @@ const ExecuteCommands: IExecuteCommands = {
|
|
|
34
34
|
`The current driver version does not define any execute methods.`
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
|
-
const
|
|
38
|
-
.map((name) => [distance(script, name), name])
|
|
39
|
-
.reduce((acc, [key, value]) => {
|
|
40
|
-
if (key in acc) {
|
|
41
|
-
acc[key].push(value);
|
|
42
|
-
} else {
|
|
43
|
-
acc[key] = [value];
|
|
44
|
-
}
|
|
45
|
-
return acc;
|
|
46
|
-
}, {});
|
|
47
|
-
const sortedMatches = _.flatten(
|
|
48
|
-
_.keys(matchesMap)
|
|
49
|
-
.sort((a, b) => parseInt(a, 10) - parseInt(b, 10))
|
|
50
|
-
.map((x) => matchesMap[x])
|
|
51
|
-
);
|
|
37
|
+
const {sorted: sortedMatches, suggestion} = rankLevenshteinCandidates(script, availableScripts);
|
|
52
38
|
throw new errors.UnsupportedOperationError(
|
|
53
|
-
|
|
39
|
+
(suggestion
|
|
40
|
+
? `Unsupported execute method '${script}', did you mean '${suggestion}'? `
|
|
41
|
+
: `Unsupported execute method '${script}'. `) +
|
|
54
42
|
`Make sure the installed ${Driver.name} is up-to-date. ` +
|
|
55
43
|
`Execute methods available in the current driver version are: ` +
|
|
56
44
|
sortedMatches.join(', ')
|
|
57
45
|
);
|
|
58
46
|
}
|
|
59
47
|
const args = validateExecuteMethodParams(protoArgs as any[], commandMetadata.params);
|
|
60
|
-
const
|
|
48
|
+
const commandName = commandMetadata.command as keyof BaseDriver<C>;
|
|
49
|
+
const command = this[commandName] as DriverCommand;
|
|
61
50
|
return await command.call(this, ...args);
|
|
62
51
|
},
|
|
63
52
|
};
|
package/lib/basedriver/core.ts
CHANGED
|
@@ -77,19 +77,10 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
|
|
77
77
|
|
|
78
78
|
noCommandTimer: NodeJS.Timeout | null;
|
|
79
79
|
|
|
80
|
-
protected _eventHistory: EventHistory;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* TODO: remove this._log and use this.log instead
|
|
84
|
-
*/
|
|
85
|
-
protected _log: AppiumLogger;
|
|
86
|
-
|
|
87
80
|
shutdownUnexpectedly: boolean;
|
|
88
81
|
|
|
89
82
|
shouldValidateCaps: boolean;
|
|
90
83
|
|
|
91
|
-
protected commandsQueueGuard: AsyncLock;
|
|
92
|
-
|
|
93
84
|
/**
|
|
94
85
|
* settings should be instantiated by drivers which extend BaseDriver, but
|
|
95
86
|
* we set it to an empty DeviceSettings instance here to make sure that the
|
|
@@ -100,6 +91,15 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
|
|
100
91
|
|
|
101
92
|
protocol?: Protocol;
|
|
102
93
|
|
|
94
|
+
protected _eventHistory: EventHistory;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* TODO: remove this._log and use this.log instead
|
|
98
|
+
*/
|
|
99
|
+
protected _log: AppiumLogger;
|
|
100
|
+
|
|
101
|
+
protected commandsQueueGuard: AsyncLock;
|
|
102
|
+
|
|
103
103
|
constructor(opts: InitialOpts = <InitialOpts>{}, shouldValidateCaps = true) {
|
|
104
104
|
super();
|
|
105
105
|
this._log = this.log; // TODO: remove references to this._log and use this.log instead
|
|
@@ -136,19 +136,6 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
|
|
136
136
|
this.settings = new DeviceSettings();
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
/**
|
|
140
|
-
* Set a callback handler if needed to execute a custom piece of code
|
|
141
|
-
* when the driver is shut down unexpectedly. Multiple calls to this method
|
|
142
|
-
* will cause the handler to be executed multiple times
|
|
143
|
-
*
|
|
144
|
-
* @param handler The code to be executed on unexpected shutdown.
|
|
145
|
-
* The function may accept one argument, which is the actual error instance, which
|
|
146
|
-
* caused the driver to shut down.
|
|
147
|
-
*/
|
|
148
|
-
onUnexpectedShutdown(handler: (...args: any[]) => void) {
|
|
149
|
-
this.eventEmitter.on(ON_UNEXPECTED_SHUTDOWN_EVENT, handler);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
139
|
/**
|
|
153
140
|
* This property is used by AppiumDriver to store the data of the
|
|
154
141
|
* specific driver sessions. This data can be later used to adjust
|
|
@@ -182,6 +169,31 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
|
|
182
169
|
return _.cloneDeep(this._eventHistory);
|
|
183
170
|
}
|
|
184
171
|
|
|
172
|
+
/**
|
|
173
|
+
* If this driver has requested proxying of bidi connections to an upstream bidi endpoint, this
|
|
174
|
+
* method should be overridden to return the URL of that websocket, to indicate that bidi
|
|
175
|
+
* proxying is enabled. Otherwise, a null return will indicate that bidi proxying should not be
|
|
176
|
+
* active and bidi commands will be handled by this driver.
|
|
177
|
+
*
|
|
178
|
+
* @returns {string | null}
|
|
179
|
+
*/
|
|
180
|
+
get bidiProxyUrl(): string | null {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Set a callback handler if needed to execute a custom piece of code
|
|
186
|
+
* when the driver is shut down unexpectedly. Multiple calls to this method
|
|
187
|
+
* will cause the handler to be executed multiple times
|
|
188
|
+
*
|
|
189
|
+
* @param handler The code to be executed on unexpected shutdown.
|
|
190
|
+
* The function may accept one argument, which is the actual error instance, which
|
|
191
|
+
* caused the driver to shut down.
|
|
192
|
+
*/
|
|
193
|
+
onUnexpectedShutdown(handler: (...args: any[]) => void) {
|
|
194
|
+
this.eventEmitter.on(ON_UNEXPECTED_SHUTDOWN_EVENT, handler);
|
|
195
|
+
}
|
|
196
|
+
|
|
185
197
|
/**
|
|
186
198
|
* API method for driver developers to log timings for important events
|
|
187
199
|
*/
|
|
@@ -327,18 +339,6 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
|
|
327
339
|
}
|
|
328
340
|
}
|
|
329
341
|
|
|
330
|
-
/**
|
|
331
|
-
* If this driver has requested proxying of bidi connections to an upstream bidi endpoint, this
|
|
332
|
-
* method should be overridden to return the URL of that websocket, to indicate that bidi
|
|
333
|
-
* proxying is enabled. Otherwise, a null return will indicate that bidi proxying should not be
|
|
334
|
-
* active and bidi commands will be handled by this driver.
|
|
335
|
-
*
|
|
336
|
-
* @returns {string | null}
|
|
337
|
-
*/
|
|
338
|
-
get bidiProxyUrl(): string | null {
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
342
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
343
343
|
proxyActive(sessionId: string): boolean {
|
|
344
344
|
return false;
|
package/lib/basedriver/driver.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import B from 'bluebird';
|
|
21
21
|
import _ from 'lodash';
|
|
22
22
|
import {fixCaps, isW3cCaps} from '../helpers/capabilities';
|
|
23
|
+
import {getLevenshteinSuggestion} from '../helpers/levenshtein-match';
|
|
23
24
|
import {calcSignature} from '../helpers/session';
|
|
24
25
|
import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
|
|
25
26
|
import {processCapabilities, validateCaps} from './capabilities';
|
|
@@ -396,11 +397,17 @@ export class BaseDriver<
|
|
|
396
397
|
}
|
|
397
398
|
|
|
398
399
|
logExtraCaps(caps: Capabilities<C>) {
|
|
399
|
-
const
|
|
400
|
+
const knownCaps = _.keys(this._desiredCapConstraints);
|
|
401
|
+
const extraCaps = _.difference(_.keys(caps), knownCaps);
|
|
400
402
|
if (extraCaps.length) {
|
|
401
403
|
this.log.warn(`The following provided capabilities were not recognized by this driver:`);
|
|
402
404
|
for (const cap of extraCaps) {
|
|
403
|
-
|
|
405
|
+
const suggestion = getLevenshteinSuggestion(cap, knownCaps);
|
|
406
|
+
this.log.warn(
|
|
407
|
+
suggestion
|
|
408
|
+
? ` ${cap} (did you mean '${suggestion}'?)`
|
|
409
|
+
: ` ${cap}`,
|
|
410
|
+
);
|
|
404
411
|
}
|
|
405
412
|
}
|
|
406
413
|
}
|
|
@@ -18,9 +18,9 @@ export class ExtensionCore {
|
|
|
18
18
|
bidiEventSubs: Record<string, string[]>;
|
|
19
19
|
bidiCommands: BidiModuleMap = BIDI_COMMANDS as BidiModuleMap;
|
|
20
20
|
_logPrefix?: string;
|
|
21
|
-
protected _log: AppiumLogger;
|
|
22
21
|
// used to handle driver events
|
|
23
22
|
readonly eventEmitter: NodeJS.EventEmitter;
|
|
23
|
+
protected _log: AppiumLogger;
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
constructor(logPrefix?: string) {
|
|
@@ -36,7 +36,7 @@ const APPLICATIONS_CACHE = new LRUCache<string, CachedAppInfoEntry>({
|
|
|
36
36
|
`expired after ${CACHED_APPS_MAX_AGE_MS}ms`
|
|
37
37
|
);
|
|
38
38
|
if (fullPath) {
|
|
39
|
-
fs.rimraf(fullPath);
|
|
39
|
+
void fs.rimraf(fullPath);
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
noDisposeOnSet: true,
|
|
@@ -67,6 +67,25 @@ process.on('exit', () => {
|
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
interface RemoteAppProps {
|
|
71
|
+
lastModified: Date | null;
|
|
72
|
+
immutable: boolean;
|
|
73
|
+
maxAge: number | null;
|
|
74
|
+
etag: string | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface RemoteAppData {
|
|
78
|
+
status: number;
|
|
79
|
+
stream: Readable;
|
|
80
|
+
headers: AxiosResponseHeaders | RawAxiosRequestHeaders;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Cache value we store (extends CachedAppInfo with optional packageHash) */
|
|
84
|
+
interface CachedAppInfoEntry extends Omit<CachedAppInfo, 'packageHash'> {
|
|
85
|
+
packageHash?: string | null;
|
|
86
|
+
fullPath?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
/**
|
|
71
90
|
* Performs initial application package configuration so the app is ready for driver use.
|
|
72
91
|
* Resolves local paths, downloads remote apps (http/https) with optional caching, and
|
|
@@ -361,19 +380,7 @@ export function generateDriverLogPrefix(obj: object | null, _sessionId?: string
|
|
|
361
380
|
return `${obj.constructor.name}@${node.getObjectId(obj).substring(0, 4)}`;
|
|
362
381
|
}
|
|
363
382
|
|
|
364
|
-
// #region Private
|
|
365
|
-
interface RemoteAppProps {
|
|
366
|
-
lastModified: Date | null;
|
|
367
|
-
immutable: boolean;
|
|
368
|
-
maxAge: number | null;
|
|
369
|
-
etag: string | null;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
interface RemoteAppData {
|
|
373
|
-
status: number;
|
|
374
|
-
stream: Readable;
|
|
375
|
-
headers: AxiosResponseHeaders | RawAxiosRequestHeaders;
|
|
376
|
-
}
|
|
383
|
+
// #region Private helpers
|
|
377
384
|
|
|
378
385
|
function parseAppLink(appLink: string): URL | {protocol?: string; pathname?: string; href?: string; search?: string} {
|
|
379
386
|
try {
|
|
@@ -562,13 +569,6 @@ function toNaturalNumber(defaultValue: number, envVarName?: string): number {
|
|
|
562
569
|
return num > 0 ? num : defaultValue;
|
|
563
570
|
}
|
|
564
571
|
|
|
565
|
-
/** Cache value we store (extends CachedAppInfo with optional packageHash) */
|
|
566
|
-
interface CachedAppInfoEntry extends Omit<CachedAppInfo, 'packageHash'> {
|
|
567
|
-
packageHash?: string | null;
|
|
568
|
-
fullPath?: string;
|
|
569
|
-
}
|
|
570
|
-
// #endregion
|
|
571
|
-
|
|
572
572
|
export default {
|
|
573
573
|
configureApp,
|
|
574
574
|
isPackageOrBundle,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type {StringRecord} from '@appium/types';
|
|
2
|
+
import {distance} from 'fastest-levenshtein';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Inclusive maximum Levenshtein edit distance for offering a "did you mean" hint.
|
|
7
|
+
* Matches with distance greater than this value are treated as unrelated.
|
|
8
|
+
*/
|
|
9
|
+
export const LEVENSHTEIN_SUGGESTION_MAX_EDIT_DISTANCE = 2;
|
|
10
|
+
|
|
11
|
+
export interface LevenshteinRankResult {
|
|
12
|
+
/** Candidates sorted by ascending edit distance from `target`, then alphabetically within ties. */
|
|
13
|
+
sorted: string[];
|
|
14
|
+
/** Closest name only if its edit distance is at most `maxEditDistance` (inclusive). */
|
|
15
|
+
suggestion: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sorts candidates by Levenshtein distance from `target` and optionally picks a suggestion
|
|
20
|
+
* when the closest match is within `maxEditDistance` edits (single pass over candidates).
|
|
21
|
+
*/
|
|
22
|
+
export function rankLevenshteinCandidates(
|
|
23
|
+
target: string,
|
|
24
|
+
candidates: readonly string[],
|
|
25
|
+
maxEditDistance: number = LEVENSHTEIN_SUGGESTION_MAX_EDIT_DISTANCE,
|
|
26
|
+
): LevenshteinRankResult {
|
|
27
|
+
if (!candidates.length) {
|
|
28
|
+
return {sorted: [], suggestion: undefined};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const matchesMap: StringRecord<string[]> = candidates
|
|
32
|
+
.map((name) => [distance(target, name), name] as const)
|
|
33
|
+
.reduce((acc, [dist, name]) => {
|
|
34
|
+
const key = String(dist);
|
|
35
|
+
if (key in acc) {
|
|
36
|
+
acc[key].push(name);
|
|
37
|
+
} else {
|
|
38
|
+
acc[key] = [name];
|
|
39
|
+
}
|
|
40
|
+
return acc;
|
|
41
|
+
}, {});
|
|
42
|
+
const sortedDistanceKeys = _.keys(matchesMap).sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
|
|
43
|
+
const sorted = _.flatten(
|
|
44
|
+
sortedDistanceKeys.map((k) => (matchesMap[k] ?? []).sort()),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const best = sorted[0];
|
|
48
|
+
const firstDistanceKey = sortedDistanceKeys[0];
|
|
49
|
+
const minDist = firstDistanceKey !== undefined ? parseInt(firstDistanceKey, 10) : NaN;
|
|
50
|
+
const suggestion = maxEditDistance >= 0 && best !== undefined && !Number.isNaN(minDist) && minDist <= maxEditDistance
|
|
51
|
+
? best
|
|
52
|
+
: undefined;
|
|
53
|
+
return {sorted, suggestion};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sorts strings by ascending Levenshtein distance from `target`.
|
|
58
|
+
* Strings with the same distance are sorted alphabetically.
|
|
59
|
+
*/
|
|
60
|
+
export function sortByLevenshteinDistance(target: string, candidates: readonly string[]): string[] {
|
|
61
|
+
return rankLevenshteinCandidates(target, candidates, Number.POSITIVE_INFINITY).sorted;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns the closest string in `candidates` by Levenshtein distance only if that
|
|
66
|
+
* distance is at most `maxEditDistance` (inclusive).
|
|
67
|
+
*/
|
|
68
|
+
export function getLevenshteinSuggestion(
|
|
69
|
+
target: string,
|
|
70
|
+
candidates: readonly string[],
|
|
71
|
+
maxEditDistance: number = LEVENSHTEIN_SUGGESTION_MAX_EDIT_DISTANCE,
|
|
72
|
+
): string | undefined {
|
|
73
|
+
return rankLevenshteinCandidates(target, candidates, maxEditDistance).suggestion;
|
|
74
|
+
}
|
|
@@ -69,14 +69,14 @@ export class ProtocolConverter {
|
|
|
69
69
|
return this._log ?? DEFAULT_LOG;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
set downstreamProtocol(value: string | null | undefined) {
|
|
73
|
-
this._downstreamProtocol = value;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
72
|
get downstreamProtocol(): string | null | undefined {
|
|
77
73
|
return this._downstreamProtocol;
|
|
78
74
|
}
|
|
79
75
|
|
|
76
|
+
set downstreamProtocol(value: string | null | undefined) {
|
|
77
|
+
this._downstreamProtocol = value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
80
|
/**
|
|
81
81
|
* Handle "crossing" endpoints for the case when upstream and downstream
|
|
82
82
|
* drivers operate different protocols.
|