appium-remote-debugger 15.0.0 → 15.0.1

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.
@@ -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
  /**
@@ -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
  /**
@@ -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;
@@ -83,7 +84,7 @@ export class RpcClient {
83
84
  /** @type {EventEmitter} */
84
85
  _targetSubscriptions;
85
86
 
86
- /** @type {[import('../types').AppIdKey, import('../types').PageIdKey] | undefined} */
87
+ /** @type {PendingTargetNotification | undefined} */
87
88
  _pendingTargetNotification;
88
89
 
89
90
  /**
@@ -454,7 +455,7 @@ export class RpcClient {
454
455
  return;
455
456
  }
456
457
 
457
- const [appIdKey, pageIdKey] = this._pendingTargetNotification;
458
+ const [appIdKey, pageIdKey, pageReadinessDetector] = this._pendingTargetNotification;
458
459
  if (appIdKey !== app) {
459
460
  log.info(
460
461
  `Received 'Target.targetCreated' event for app '${app}' with no pending request: ${JSON.stringify(targetInfo)}`
@@ -470,6 +471,24 @@ export class RpcClient {
470
471
  }
471
472
  const timer = new timing.Timer().start();
472
473
 
474
+ const adjustPageReadinessDetector = () => {
475
+ if (!pageReadinessDetector) {
476
+ return;
477
+ }
478
+
479
+ const elapsedMs = timer.getDuration().asMilliSeconds;
480
+ if (elapsedMs >= pageReadinessDetector.timeoutMs) {
481
+ log.warn(
482
+ `Page '${pageIdKey}' took too long to initialize, skipping readiness check`
483
+ );
484
+ return;
485
+ }
486
+ return {
487
+ timeoutMs: pageReadinessDetector.timeoutMs - elapsedMs,
488
+ readinessDetector: pageReadinessDetector.readinessDetector,
489
+ };
490
+ };
491
+
473
492
  if (targetInfo.isProvisional) {
474
493
  log.debug(
475
494
  `Provisional target created for app '${appIdKey}' and page '${pageIdKey}': '${JSON.stringify(targetInfo)}'`
@@ -478,8 +497,9 @@ export class RpcClient {
478
497
  this._provisionedPages.add(pageIdKey);
479
498
  try {
480
499
  await this.targets[appIdKey].lock.acquire(pageIdKey, async () => {
500
+ let wasInitialized = false;
481
501
  try {
482
- await this._initializePage(appIdKey, pageIdKey, targetInfo.targetId);
502
+ wasInitialized = await this._initializePage(appIdKey, pageIdKey, targetInfo.targetId);
483
503
  } finally {
484
504
  if (targetInfo.isPaused) {
485
505
  await this._resumeTarget(appIdKey, pageIdKey, targetInfo.targetId);
@@ -489,6 +509,11 @@ export class RpcClient {
489
509
  );
490
510
  }
491
511
  }
512
+ if (wasInitialized) {
513
+ await this._waitForPageReadiness(
514
+ appIdKey, pageIdKey, targetInfo.targetId, adjustPageReadinessDetector()
515
+ );
516
+ }
492
517
  });
493
518
  } catch (e) {
494
519
  log.warn(
@@ -522,18 +547,24 @@ export class RpcClient {
522
547
 
523
548
  try {
524
549
  await this.targets[appIdKey].lock.acquire(pageIdKey, async () => {
550
+ let wasInitialized = false;
525
551
  try {
526
552
  if (this._provisionedPages.has(pageIdKey)) {
527
553
  log.debug(`Page '${pageIdKey}' has been already provisioned`);
528
554
  this._provisionedPages.delete(pageIdKey);
529
555
  } else {
530
- await this._initializePage(appIdKey, pageIdKey);
556
+ wasInitialized = await this._initializePage(appIdKey, pageIdKey);
531
557
  }
532
558
  } finally {
533
559
  if (targetInfo.isPaused) {
534
560
  await this._resumeTarget(appIdKey, pageIdKey, targetInfo.targetId);
535
561
  }
536
562
  }
563
+ if (wasInitialized) {
564
+ await this._waitForPageReadiness(
565
+ appIdKey, pageIdKey, targetInfo.targetId, adjustPageReadinessDetector()
566
+ );
567
+ }
537
568
  });
538
569
  } catch (e) {
539
570
  log.warn(e.message);
@@ -633,10 +664,11 @@ export class RpcClient {
633
664
  /**
634
665
  * @param {import('../types').AppIdKey} appIdKey
635
666
  * @param {import('../types').PageIdKey} pageIdKey
667
+ * @param {PageReadinessDetector} [pageReadinessDetector]
636
668
  * @returns {Promise<void>}
637
669
  */
638
- async selectPage (appIdKey, pageIdKey) {
639
- this._pendingTargetNotification = [appIdKey, pageIdKey];
670
+ async selectPage (appIdKey, pageIdKey, pageReadinessDetector) {
671
+ this._pendingTargetNotification = [appIdKey, pageIdKey, pageReadinessDetector];
640
672
  this._provisionedPages.clear();
641
673
 
642
674
  // go through the steps that the Desktop Safari system
@@ -900,6 +932,56 @@ export class RpcClient {
900
932
  }
901
933
  }
902
934
 
935
+ /**
936
+ *
937
+ * @param {import('../types').AppIdKey} appIdKey
938
+ * @param {import('../types').PageIdKey} pageIdKey
939
+ * @param {import('../types').TargetId} targetId
940
+ * @param {PageReadinessDetector} [pageReadinessDetector]
941
+ * @returns {Promise<void>}
942
+ */
943
+ async _waitForPageReadiness(appIdKey, pageIdKey, targetId, pageReadinessDetector) {
944
+ if (!pageReadinessDetector) {
945
+ return;
946
+ }
947
+
948
+ log.debug(`Waiting up to ${pageReadinessDetector.timeoutMs}ms for page readiness`);
949
+ const timer = new timing.Timer().start();
950
+ while (pageReadinessDetector.timeoutMs - timer.getDuration().asMilliSeconds > 0) {
951
+ /** @type {string} */
952
+ let readyState;
953
+ try {
954
+ const commandTimeoutMs = Math.max(
955
+ 100,
956
+ Math.trunc((pageReadinessDetector.timeoutMs - timer.getDuration().asMilliSeconds) * 0.8)
957
+ );
958
+ const rawResult = await B.resolve(this.send('Runtime.evaluate', {
959
+ expression: 'document.readyState;',
960
+ returnByValue: true,
961
+ appIdKey,
962
+ pageIdKey,
963
+ targetId,
964
+ })).timeout(commandTimeoutMs);
965
+ readyState = convertJavascriptEvaluationResult(rawResult);
966
+ } catch (e) {
967
+ log.debug(`Cannot determine page readiness: ${e.message}`);
968
+ continue;
969
+ }
970
+ if (pageReadinessDetector.readinessDetector(readyState)) {
971
+ log.info(
972
+ `Page '${pageIdKey}' for app '${appIdKey}' is ready after ` +
973
+ `${timer.getDuration().asMilliSeconds}ms`
974
+ );
975
+ return;
976
+ }
977
+ await B.delay(100);
978
+ }
979
+ log.warn(
980
+ `Page '${pageIdKey}' for app '${appIdKey}' is not ready after ` +
981
+ `${timer.getDuration().asMilliSeconds}ms. Continuing anyway`
982
+ );
983
+ }
984
+
903
985
  /**
904
986
  *
905
987
  * @param {import('../types').AppIdKey} appIdKey
@@ -943,4 +1025,15 @@ export default RpcClient;
943
1025
  /**
944
1026
  * @typedef {PageDict & {provisional?: import('../types').ProvisionalTargetInfo, lock: AsyncLock}} PagesToTargets
945
1027
  * @typedef {{[key: import('../types').AppIdKey]: PagesToTargets}} AppToTargetsMap
946
- */
1028
+ */
1029
+
1030
+ /**
1031
+ * @typedef {Object} PageReadinessDetector
1032
+ * @property {number} timeoutMs
1033
+ * @property {(readyState: string) => boolean} readinessDetector
1034
+ */
1035
+
1036
+ /**
1037
+ * @typedef {[import('../types').AppIdKey, import('../types').PageIdKey, PageReadinessDetector | undefined]} PendingTargetNotification
1038
+ */
1039
+
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.1",
8
8
  "author": "Appium Contributors",
9
9
  "license": "Apache-2.0",
10
10
  "repository": {