appium-remote-debugger 15.0.0 → 15.0.2

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/mixins/connect.js +5 -4
  3. package/build/lib/mixins/connect.js.map +1 -1
  4. package/build/lib/mixins/events.js +1 -2
  5. package/build/lib/mixins/events.js.map +1 -1
  6. package/build/lib/mixins/execute.js +4 -4
  7. package/build/lib/mixins/execute.js.map +1 -1
  8. package/build/lib/mixins/navigate.d.ts.map +1 -1
  9. package/build/lib/mixins/navigate.js +13 -12
  10. package/build/lib/mixins/navigate.js.map +1 -1
  11. package/build/lib/remote-debugger-real-device.js +1 -0
  12. package/build/lib/remote-debugger-real-device.js.map +1 -1
  13. package/build/lib/remote-debugger.d.ts +0 -1
  14. package/build/lib/remote-debugger.d.ts.map +1 -1
  15. package/build/lib/remote-debugger.js +75 -42
  16. package/build/lib/remote-debugger.js.map +1 -1
  17. package/build/lib/rpc/rpc-client-simulator.js +10 -0
  18. package/build/lib/rpc/rpc-client-simulator.js.map +1 -1
  19. package/build/lib/rpc/rpc-client.d.ts +22 -4
  20. package/build/lib/rpc/rpc-client.d.ts.map +1 -1
  21. package/build/lib/rpc/rpc-client.js +191 -25
  22. package/build/lib/rpc/rpc-client.js.map +1 -1
  23. package/build/lib/types.d.ts +1 -1
  24. package/build/lib/types.d.ts.map +1 -1
  25. package/build/lib/utils.d.ts +1 -1
  26. package/build/lib/utils.d.ts.map +1 -1
  27. package/build/lib/utils.js +2 -2
  28. package/build/lib/utils.js.map +1 -1
  29. package/build/tsconfig.tsbuildinfo +1 -1
  30. package/lib/mixins/connect.js +5 -5
  31. package/lib/mixins/execute.js +5 -5
  32. package/lib/mixins/navigate.js +13 -14
  33. package/lib/remote-debugger.ts +0 -4
  34. package/lib/rpc/rpc-client.js +181 -26
  35. package/lib/types.ts +1 -1
  36. package/lib/utils.js +1 -1
  37. package/package.json +1 -1
@@ -173,11 +173,11 @@ export async function selectPage (appIdKey, pageIdKey, skipReadyCheck = false) {
173
173
 
174
174
  const timer = new timing.Timer().start();
175
175
 
176
- await this.requireRpcClient().selectPage(fullAppIdKey, pageIdKey);
177
-
178
- if (!skipReadyCheck) {
179
- await this.waitForDom();
180
- }
176
+ const pageReadinessDetector = skipReadyCheck ? undefined : {
177
+ timeoutMs: this.pageLoadMs,
178
+ readinessDetector: (/** @type {string} */ readyState) => this.isPageLoadingCompleted(readyState),
179
+ };
180
+ await this.requireRpcClient().selectPage(fullAppIdKey, pageIdKey, pageReadinessDetector);
181
181
 
182
182
  this.log.debug(`Selected page after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
183
183
  }
@@ -2,7 +2,7 @@ import { errors } from '@appium/base-driver';
2
2
  import {
3
3
  checkParams,
4
4
  simpleStringify,
5
- convertResult,
5
+ convertJavascriptEvaluationResult,
6
6
  RESPONSE_LOG_LENGTH,
7
7
  } from '../utils';
8
8
  import { getScriptForAtom } from '../atoms';
@@ -123,7 +123,7 @@ export async function executeAtomAsync (atom, args = [], frames = []) {
123
123
  );
124
124
  } catch {}
125
125
  }
126
- return convertResult(res);
126
+ return convertJavascriptEvaluationResult(res);
127
127
  }
128
128
 
129
129
  /**
@@ -144,7 +144,7 @@ export async function execute (command, override) {
144
144
  }
145
145
 
146
146
  const rpcClient = this.requireRpcClient(true);
147
- await rpcClient.waitForPageInitialization(
147
+ await rpcClient.waitForPage(
148
148
  /** @type {import('../types').AppIdKey} */ (appIdKey),
149
149
  /** @type {import('../types').PageIdKey} */ (pageIdKey)
150
150
  );
@@ -155,7 +155,7 @@ export async function execute (command, override) {
155
155
  appIdKey,
156
156
  pageIdKey,
157
157
  });
158
- return convertResult(res);
158
+ return convertJavascriptEvaluationResult(res);
159
159
  }
160
160
 
161
161
  /**
@@ -184,7 +184,7 @@ export async function callFunction (objectId, fn, args) {
184
184
  pageIdKey,
185
185
  });
186
186
 
187
- return convertResult(res);
187
+ return convertJavascriptEvaluationResult(res);
188
188
  }
189
189
 
190
190
  /**
@@ -20,11 +20,11 @@ const PAGE_READINESS_CHECK_INTERVAL_MS = 50;
20
20
  /**
21
21
  * pageLoadStrategy in WebDriver definitions.
22
22
  */
23
- const PAGE_LOAD_STRATEGY = {
23
+ const PAGE_LOAD_STRATEGY = Object.freeze({
24
24
  EAGER: 'eager',
25
25
  NONE: 'none',
26
26
  NORMAL: 'normal'
27
- };
27
+ });
28
28
 
29
29
  /**
30
30
  * @this {RemoteDebugger}
@@ -53,18 +53,17 @@ export function cancelPageLoad () {
53
53
  * @returns {boolean}
54
54
  */
55
55
  export function isPageLoadingCompleted (readyState) {
56
- const _pageLoadStrategy = _.toLower(getPageLoadStartegy(this));
57
- if (_pageLoadStrategy === PAGE_LOAD_STRATEGY.NONE) {
58
- return true;
56
+ const pageLoadStrategy = _.toLower(getPageLoadStartegy(this));
57
+ switch (pageLoadStrategy) {
58
+ case PAGE_LOAD_STRATEGY.EAGER:
59
+ // This could include 'interactive' or 'complete'
60
+ return readyState !== 'loading';
61
+ case PAGE_LOAD_STRATEGY.NONE:
62
+ return true;
63
+ case PAGE_LOAD_STRATEGY.NORMAL:
64
+ default:
65
+ return readyState === 'complete';
59
66
  }
60
-
61
- if (_pageLoadStrategy === PAGE_LOAD_STRATEGY.EAGER) {
62
- // This could include 'interactive' or 'complete'
63
- return readyState !== 'loading';
64
- }
65
-
66
- // Default behavior. It includes pageLoadStrategy is 'normal' as well.
67
- return readyState === 'complete';
68
67
  }
69
68
 
70
69
  /**
@@ -183,7 +182,7 @@ export async function navToUrl (url) {
183
182
 
184
183
  this.log.debug(`Navigating to new URL: '${url}'`);
185
184
  setNavigatingToPage(this, true);
186
- await rpcClient.waitForPageInitialization(
185
+ await rpcClient.waitForPage(
187
186
  /** @type {import('../types').AppIdKey} */ (appIdKey),
188
187
  /** @type {import('../types').PageIdKey} */ (pageIdKey)
189
188
  );
@@ -49,7 +49,6 @@ export class RemoteDebugger extends EventEmitter {
49
49
  protected readonly _platformVersion?: string;
50
50
  protected readonly _isSafari: boolean;
51
51
  protected readonly _includeSafari: boolean;
52
- protected readonly _useNewSafari: boolean;
53
52
  protected readonly _garbageCollectOnExecute: boolean;
54
53
  protected readonly _host?: string;
55
54
  protected readonly _port?: number;
@@ -120,7 +119,6 @@ export class RemoteDebugger extends EventEmitter {
120
119
  platformVersion,
121
120
  isSafari = true,
122
121
  includeSafari = false,
123
- useNewSafari = false,
124
122
  pageLoadMs,
125
123
  host,
126
124
  port = REMOTE_DEBUGGER_PORT,
@@ -142,10 +140,8 @@ export class RemoteDebugger extends EventEmitter {
142
140
  this._platformVersion = platformVersion;
143
141
  this._isSafari = isSafari;
144
142
  this._includeSafari = includeSafari;
145
- this._useNewSafari = useNewSafari;
146
143
  this._pageLoadMs = pageLoadMs;
147
144
  this._allowNavigationWithoutReload = false;
148
- this.log.debug(`useNewSafari --> ${this._useNewSafari}`);
149
145
 
150
146
  this._garbageCollectOnExecute = garbageCollectOnExecute;
151
147
 
@@ -7,6 +7,7 @@ import RpcMessageHandler from './rpc-message-handler';
7
7
  import { util, timing } from '@appium/support';
8
8
  import { EventEmitter } from 'node:events';
9
9
  import AsyncLock from 'async-lock';
10
+ import { convertJavascriptEvaluationResult } from '../utils';
10
11
 
11
12
  const DATA_LOG_LENGTH = {length: 200};
12
13
  const MIN_WAIT_FOR_TARGET_TIMEOUT_MS = 30000;
@@ -23,6 +24,7 @@ const NO_TARGET_PRESENT_YET_ERRORS = [
23
24
  ];
24
25
  export const NEW_APP_CONNECTED_ERROR = 'New application has connected';
25
26
  export const EMPTY_PAGE_DICTIONARY_ERROR = 'Empty page dictionary received';
27
+ const ON_PAGE_INITIALIZED_EVENT = 'onPageInitialized';
26
28
 
27
29
 
28
30
  export class RpcClient {
@@ -83,7 +85,7 @@ export class RpcClient {
83
85
  /** @type {EventEmitter} */
84
86
  _targetSubscriptions;
85
87
 
86
- /** @type {[import('../types').AppIdKey, import('../types').PageIdKey] | undefined} */
88
+ /** @type {PendingTargetNotification | undefined} */
87
89
  _pendingTargetNotification;
88
90
 
89
91
  /**
@@ -128,6 +130,8 @@ export class RpcClient {
128
130
  this._targets = {};
129
131
  this._targetSubscriptions = new EventEmitter();
130
132
  this._provisionedPages = new Set();
133
+ this._pageSelectionLock = new AsyncLock();
134
+ this._pageSelectionMonitor = new EventEmitter();
131
135
 
132
136
  this.remoteMessages = new RemoteMessages();
133
137
 
@@ -454,7 +458,7 @@ export class RpcClient {
454
458
  return;
455
459
  }
456
460
 
457
- const [appIdKey, pageIdKey] = this._pendingTargetNotification;
461
+ const [appIdKey, pageIdKey, pageReadinessDetector] = this._pendingTargetNotification;
458
462
  if (appIdKey !== app) {
459
463
  log.info(
460
464
  `Received 'Target.targetCreated' event for app '${app}' with no pending request: ${JSON.stringify(targetInfo)}`
@@ -470,6 +474,24 @@ export class RpcClient {
470
474
  }
471
475
  const timer = new timing.Timer().start();
472
476
 
477
+ const adjustPageReadinessDetector = () => {
478
+ if (!pageReadinessDetector) {
479
+ return;
480
+ }
481
+
482
+ const elapsedMs = timer.getDuration().asMilliSeconds;
483
+ if (elapsedMs >= pageReadinessDetector.timeoutMs) {
484
+ log.warn(
485
+ `Page '${pageIdKey}' took too long to initialize, skipping readiness check`
486
+ );
487
+ return;
488
+ }
489
+ return {
490
+ timeoutMs: pageReadinessDetector.timeoutMs - elapsedMs,
491
+ readinessDetector: pageReadinessDetector.readinessDetector,
492
+ };
493
+ };
494
+
473
495
  if (targetInfo.isProvisional) {
474
496
  log.debug(
475
497
  `Provisional target created for app '${appIdKey}' and page '${pageIdKey}': '${JSON.stringify(targetInfo)}'`
@@ -478,8 +500,9 @@ export class RpcClient {
478
500
  this._provisionedPages.add(pageIdKey);
479
501
  try {
480
502
  await this.targets[appIdKey].lock.acquire(pageIdKey, async () => {
503
+ let wasInitialized = false;
481
504
  try {
482
- await this._initializePage(appIdKey, pageIdKey, targetInfo.targetId);
505
+ wasInitialized = await this._initializePage(appIdKey, pageIdKey, targetInfo.targetId);
483
506
  } finally {
484
507
  if (targetInfo.isPaused) {
485
508
  await this._resumeTarget(appIdKey, pageIdKey, targetInfo.targetId);
@@ -489,6 +512,11 @@ export class RpcClient {
489
512
  );
490
513
  }
491
514
  }
515
+ if (wasInitialized) {
516
+ await this._waitForPageReadiness(
517
+ appIdKey, pageIdKey, targetInfo.targetId, adjustPageReadinessDetector()
518
+ );
519
+ }
492
520
  });
493
521
  } catch (e) {
494
522
  log.warn(
@@ -522,21 +550,32 @@ export class RpcClient {
522
550
 
523
551
  try {
524
552
  await this.targets[appIdKey].lock.acquire(pageIdKey, async () => {
553
+ let wasInitialized = false;
525
554
  try {
526
555
  if (this._provisionedPages.has(pageIdKey)) {
527
556
  log.debug(`Page '${pageIdKey}' has been already provisioned`);
528
557
  this._provisionedPages.delete(pageIdKey);
529
558
  } else {
530
- await this._initializePage(appIdKey, pageIdKey);
559
+ wasInitialized = await this._initializePage(appIdKey, pageIdKey);
531
560
  }
532
561
  } finally {
533
562
  if (targetInfo.isPaused) {
534
563
  await this._resumeTarget(appIdKey, pageIdKey, targetInfo.targetId);
535
564
  }
536
565
  }
566
+ if (wasInitialized) {
567
+ await this._waitForPageReadiness(
568
+ appIdKey, pageIdKey, targetInfo.targetId, adjustPageReadinessDetector()
569
+ );
570
+ }
537
571
  });
538
572
  } catch (e) {
539
573
  log.warn(e.message);
574
+ } finally {
575
+ // Target creation is happening after provisioning,
576
+ // which means the above lock would be already released
577
+ // after provisioning is completed.
578
+ this._pageSelectionMonitor.emit(ON_PAGE_INITIALIZED_EVENT, appIdKey, pageIdKey);
540
579
  }
541
580
  }
542
581
 
@@ -633,32 +672,73 @@ export class RpcClient {
633
672
  /**
634
673
  * @param {import('../types').AppIdKey} appIdKey
635
674
  * @param {import('../types').PageIdKey} pageIdKey
675
+ * @param {PageReadinessDetector} [pageReadinessDetector]
636
676
  * @returns {Promise<void>}
637
677
  */
638
- async selectPage (appIdKey, pageIdKey) {
639
- this._pendingTargetNotification = [appIdKey, pageIdKey];
640
- this._provisionedPages.clear();
678
+ async selectPage (appIdKey, pageIdKey, pageReadinessDetector) {
679
+ await this._pageSelectionLock.acquire(toPageSelectionKey(appIdKey, pageIdKey), async () => {
680
+ this._pendingTargetNotification = [appIdKey, pageIdKey, pageReadinessDetector];
681
+ this._provisionedPages.clear();
641
682
 
642
- // go through the steps that the Desktop Safari system
643
- // goes through to initialize the Web Inspector session
683
+ // go through the steps that the Desktop Safari system
684
+ // goes through to initialize the Web Inspector session
644
685
 
645
- const sendOpts = {
646
- appIdKey,
647
- pageIdKey,
648
- };
686
+ const sendOpts = {
687
+ appIdKey,
688
+ pageIdKey,
689
+ };
649
690
 
650
- // highlight and then un-highlight the webview
651
- for (const enabled of [true, false]) {
652
- await this.send('indicateWebView', Object.assign({
653
- enabled,
654
- }, sendOpts), false);
655
- }
691
+ const timeoutMs = Math.trunc(Math.max(this.pageLoadTimeoutMs ?? 0, PAGE_INIT_TIMEOUT_MS) * 1.2);
692
+ const timer = new timing.Timer().start();
656
693
 
657
- await this.send('setSenderKey', sendOpts);
658
- log.debug('Sender key set');
694
+ const setupWebview = async () => {
695
+ // highlight and then un-highlight the webview
696
+ for (const enabled of [true, false]) {
697
+ await this.send('indicateWebView', Object.assign({
698
+ enabled,
699
+ }, sendOpts), false);
700
+ }
701
+ await this.send('setSenderKey', sendOpts);
702
+ };
703
+ await B.resolve(setupWebview())
704
+ .timeout(timeoutMs, `Cannot set up page '${pageIdKey}' for app '${appIdKey}' within ${timeoutMs}ms`);
659
705
 
660
- await this.waitForTarget(appIdKey, pageIdKey);
661
- await this.waitForPageInitialization(appIdKey, pageIdKey);
706
+ const msLeft = Math.max(timeoutMs - Math.trunc(timer.getDuration().asMilliSeconds), 1000);
707
+ log.debug(
708
+ `Waiting up to ${msLeft}ms for page '${pageIdKey}' of app '${appIdKey}' to be selected`
709
+ );
710
+ await new Promise((resolve) => {
711
+ const onPageInitialized = (
712
+ /** @type {import("../types").AppIdKey} */ notifiedAppIdKey,
713
+ /** @type {import("../types").PageIdKey} */ notifiedPageIdKey
714
+ ) => {
715
+ const timeoutHandler = setTimeout(() => {
716
+ this._pageSelectionMonitor.off(ON_PAGE_INITIALIZED_EVENT, onPageInitialized);
717
+ log.warn(
718
+ `Page '${pageIdKey}' for app '${appIdKey}' has not been selected ` +
719
+ `within ${timer.getDuration().asMilliSeconds}ms. Continuing anyway`
720
+ );
721
+ resolve(false);
722
+ }, msLeft);
723
+
724
+ if (notifiedAppIdKey === appIdKey && notifiedPageIdKey === pageIdKey) {
725
+ clearTimeout(timeoutHandler);
726
+ this._pageSelectionMonitor.off(ON_PAGE_INITIALIZED_EVENT, onPageInitialized);
727
+ log.debug(
728
+ `Selected the page ${pageIdKey}@${appIdKey} after ${timer.getDuration().asMilliSeconds}ms`
729
+ );
730
+ resolve(true);
731
+ } else {
732
+ log.debug(
733
+ `Got notified that page ${notifiedPageIdKey}@${notifiedAppIdKey} is initialized, ` +
734
+ `but we are waiting for ${pageIdKey}@${appIdKey}. Continuing to wait`
735
+ );
736
+ }
737
+ };
738
+
739
+ this._pageSelectionMonitor.on(ON_PAGE_INITIALIZED_EVENT, onPageInitialized);
740
+ });
741
+ });
662
742
  }
663
743
 
664
744
  /**
@@ -904,15 +984,69 @@ export class RpcClient {
904
984
  *
905
985
  * @param {import('../types').AppIdKey} appIdKey
906
986
  * @param {import('../types').PageIdKey} pageIdKey
987
+ * @param {import('../types').TargetId} targetId
988
+ * @param {PageReadinessDetector} [pageReadinessDetector]
989
+ * @returns {Promise<void>}
907
990
  */
908
- async waitForPageInitialization (appIdKey, pageIdKey) {
991
+ async _waitForPageReadiness(appIdKey, pageIdKey, targetId, pageReadinessDetector) {
992
+ if (!pageReadinessDetector) {
993
+ return;
994
+ }
995
+
996
+ log.debug(`Waiting up to ${pageReadinessDetector.timeoutMs}ms for page readiness`);
997
+ const timer = new timing.Timer().start();
998
+ while (pageReadinessDetector.timeoutMs - timer.getDuration().asMilliSeconds > 0) {
999
+ /** @type {string} */
1000
+ let readyState;
1001
+ try {
1002
+ const commandTimeoutMs = Math.max(
1003
+ 100,
1004
+ Math.trunc((pageReadinessDetector.timeoutMs - timer.getDuration().asMilliSeconds) * 0.8)
1005
+ );
1006
+ const rawResult = await B.resolve(this.send('Runtime.evaluate', {
1007
+ expression: 'document.readyState;',
1008
+ returnByValue: true,
1009
+ appIdKey,
1010
+ pageIdKey,
1011
+ targetId,
1012
+ })).timeout(commandTimeoutMs);
1013
+ readyState = convertJavascriptEvaluationResult(rawResult);
1014
+ } catch (e) {
1015
+ log.debug(`Cannot determine page readiness: ${e.message}`);
1016
+ continue;
1017
+ }
1018
+ if (pageReadinessDetector.readinessDetector(readyState)) {
1019
+ log.info(
1020
+ `Page '${pageIdKey}' for app '${appIdKey}' is ready after ` +
1021
+ `${timer.getDuration().asMilliSeconds}ms`
1022
+ );
1023
+ return;
1024
+ }
1025
+ await B.delay(100);
1026
+ }
1027
+ log.warn(
1028
+ `Page '${pageIdKey}' for app '${appIdKey}' is not ready after ` +
1029
+ `${timer.getDuration().asMilliSeconds}ms. Continuing anyway`
1030
+ );
1031
+ }
1032
+
1033
+ /**
1034
+ *
1035
+ * @param {import('../types').AppIdKey} appIdKey
1036
+ * @param {import('../types').PageIdKey} pageIdKey
1037
+ * @returns {Promise<void>}
1038
+ */
1039
+ async waitForPage (appIdKey, pageIdKey) {
909
1040
  const appTargetsMap = this.targets[appIdKey];
910
1041
  if (!appTargetsMap) {
911
1042
  throw new Error(`No targets found for app '${appIdKey}'`);
912
1043
  }
913
1044
  const lock = appTargetsMap.lock;
914
1045
  const timer = new timing.Timer().start();
915
- await lock.acquire(pageIdKey, async () => await B.delay(0));
1046
+ await Promise.all([
1047
+ lock.acquire(pageIdKey, async () => await B.delay(0)),
1048
+ this._pageSelectionLock.acquire(toPageSelectionKey(appIdKey, pageIdKey), async () => await B.delay(0))
1049
+ ]);
916
1050
  const durationMs = timer.getDuration().asMilliSeconds;
917
1051
  if (durationMs > 10) {
918
1052
  log.debug(`Waited ${durationMs}ms until the page ${pageIdKey}@${appIdKey} is initialized`);
@@ -920,6 +1054,16 @@ export class RpcClient {
920
1054
  }
921
1055
  }
922
1056
 
1057
+ /**
1058
+ *
1059
+ * @param {import('../types').AppIdKey} appIdKey
1060
+ * @param {import('../types').PageIdKey} pageIdKey
1061
+ * @returns {string}
1062
+ */
1063
+ function toPageSelectionKey(appIdKey, pageIdKey) {
1064
+ return `${appIdKey}:${pageIdKey}`;
1065
+ }
1066
+
923
1067
  export default RpcClient;
924
1068
 
925
1069
  /**
@@ -943,4 +1087,15 @@ export default RpcClient;
943
1087
  /**
944
1088
  * @typedef {PageDict & {provisional?: import('../types').ProvisionalTargetInfo, lock: AsyncLock}} PagesToTargets
945
1089
  * @typedef {{[key: import('../types').AppIdKey]: PagesToTargets}} AppToTargetsMap
946
- */
1090
+ */
1091
+
1092
+ /**
1093
+ * @typedef {Object} PageReadinessDetector
1094
+ * @property {number} timeoutMs
1095
+ * @property {(readyState: string) => boolean} readinessDetector
1096
+ */
1097
+
1098
+ /**
1099
+ * @typedef {[import('../types').AppIdKey, import('../types').PageIdKey, PageReadinessDetector | undefined]} PendingTargetNotification
1100
+ */
1101
+
package/lib/types.ts CHANGED
@@ -49,7 +49,7 @@ export interface RemoteDebuggerOptions {
49
49
  platformVersion?: string;
50
50
  isSafari?: boolean;
51
51
  includeSafari?: boolean;
52
- /** for web inspector, whether this is a new Safari instance */
52
+ /** @deprecated - deprecated for removal, not used anywhere */
53
53
  useNewSafari?: boolean;
54
54
  /** the time, in ms, that should be waited for page loading */
55
55
  pageLoadMs?: number;
package/lib/utils.js CHANGED
@@ -157,7 +157,7 @@ export function deferredPromise () {
157
157
  * @param {any} res
158
158
  * @returns {any}
159
159
  */
160
- export function convertResult (res) {
160
+ export function convertJavascriptEvaluationResult (res) {
161
161
  if (_.isUndefined(res)) {
162
162
  throw new Error(`Did not get OK result from remote debugger. Result was: ${_.truncate(simpleStringify(res), {length: RESPONSE_LOG_LENGTH})}`);
163
163
  } else if (_.isString(res)) {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "keywords": [
5
5
  "appium"
6
6
  ],
7
- "version": "15.0.0",
7
+ "version": "15.0.2",
8
8
  "author": "Appium Contributors",
9
9
  "license": "Apache-2.0",
10
10
  "repository": {