appium-remote-debugger 14.0.2 → 14.0.4

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.
@@ -12,7 +12,6 @@ import _ from 'lodash';
12
12
  import {
13
13
  getAppIdKey,
14
14
  getPageIdKey,
15
- getPageLoading,
16
15
  getGarbageCollectOnExecute,
17
16
  } from './property-accessors';
18
17
 
@@ -31,7 +30,7 @@ const RPC_RESPONSE_TIMEOUT_MS = 5000;
31
30
  export async function executeAtom (atom, args = [], frames = []) {
32
31
  this.log.debug(`Executing atom '${atom}' with 'args=${JSON.stringify(args)}; frames=${frames}'`);
33
32
  const script = await getScriptForAtom(atom, args, frames);
34
- const value = await this.execute(script, true);
33
+ const value = await this.execute(script);
35
34
  this.log.debug(`Received result for atom '${atom}' execution: ${_.truncate(simpleStringify(value), {length: RESPONSE_LOG_LENGTH})}`);
36
35
  return value;
37
36
  }
@@ -130,16 +129,11 @@ export async function executeAtomAsync (atom, args = [], frames = []) {
130
129
  /**
131
130
  * @this {RemoteDebugger}
132
131
  * @param {string} command
133
- * @param {boolean} [override]
132
+ * @param {boolean} [override] - Deprecated and unused
134
133
  * @returns {Promise<any>}
135
134
  */
135
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
136
136
  export async function execute (command, override) {
137
- // if the page is not loaded yet, wait for it
138
- if (getPageLoading(this) && !override) {
139
- this.log.debug('Trying to execute but page is not loaded.');
140
- await this.waitForDom();
141
- }
142
-
143
137
  const {appIdKey, pageIdKey} = checkParams({
144
138
  appIdKey: getAppIdKey(this),
145
139
  pageIdKey: getPageIdKey(this),
@@ -73,12 +73,9 @@ export function isPageLoadingCompleted (readyState) {
73
73
  * @returns {Promise<void>}
74
74
  */
75
75
  export async function waitForDom (startPageLoadTimer) {
76
- this.log.debug('Waiting for page readiness');
77
76
  const readinessTimeoutMs = this.pageLoadMs;
78
- if (!_.isFunction(startPageLoadTimer?.getDuration)) {
79
- this.log.debug(`Page load timer not a timer. Creating new timer`);
80
- startPageLoadTimer = new timing.Timer().start();
81
- }
77
+ this.log.debug(`Waiting up to ${readinessTimeoutMs}ms for the page to be ready`);
78
+ const timer = startPageLoadTimer ?? new timing.Timer().start();
82
79
 
83
80
  let isPageLoading = true;
84
81
  setPageLoading(this, true);
@@ -88,8 +85,7 @@ export async function waitForDom (startPageLoadTimer) {
88
85
  let retry = 0;
89
86
  while (isPageLoading) {
90
87
  // if we are ready, or we've spend too much time on this
91
- // @ts-ignore startPageLoadTimer is defined here
92
- const elapsedMs = startPageLoadTimer.getDuration().asMilliSeconds;
88
+ const elapsedMs = timer.getDuration().asMilliSeconds;
93
89
  // exponential retry
94
90
  const intervalMs = Math.min(
95
91
  PAGE_READINESS_CHECK_INTERVAL_MS * Math.pow(2, retry),
@@ -105,7 +101,8 @@ export async function waitForDom (startPageLoadTimer) {
105
101
  return;
106
102
  }
107
103
 
108
- if (await this.checkPageIsReady()) {
104
+ const maxWaitMs = (readinessTimeoutMs - elapsedMs) * 0.95;
105
+ if (await this.checkPageIsReady(maxWaitMs)) {
109
106
  if (isPageLoading) {
110
107
  this.log.debug(`Page is ready in ${elapsedMs}ms`);
111
108
  isPageLoading = false;
@@ -113,7 +110,9 @@ export async function waitForDom (startPageLoadTimer) {
113
110
  return;
114
111
  }
115
112
  if (elapsedMs > readinessTimeoutMs) {
116
- this.log.info(`Timed out after ${readinessTimeoutMs}ms of waiting for the page readiness. Continuing anyway`);
113
+ this.log.info(
114
+ `Timed out after ${readinessTimeoutMs}ms of waiting for the page readiness. Continuing anyway`
115
+ );
117
116
  isPageLoading = false;
118
117
  return;
119
118
  }
@@ -145,16 +144,21 @@ export async function checkPageIsReady (timeoutMs) {
145
144
  const readyCmd = 'document.readyState;';
146
145
  const actualTimeoutMs = timeoutMs ?? getPageReadyTimeout(this);
147
146
  try {
148
- const readyState = await B.resolve(this.execute(readyCmd, true))
147
+ const readyState = await B.resolve(this.execute(readyCmd))
149
148
  .timeout(actualTimeoutMs);
150
- this.log.debug(`Document readyState is '${readyState}'. ` +
151
- `The pageLoadStrategy is '${getPageLoadStartegy(this) ?? PAGE_LOAD_STRATEGY.NORMAL}'`);
149
+ this.log.debug(
150
+ JSON.stringify({
151
+ readyState,
152
+ pageLoadStrategy: getPageLoadStartegy(this) ?? PAGE_LOAD_STRATEGY.NORMAL,
153
+ })
154
+ );
152
155
  return this.isPageLoadingCompleted(readyState);
153
156
  } catch (err) {
154
- if (!(err instanceof BTimeoutError)) {
155
- throw err;
157
+ if (err instanceof BTimeoutError) {
158
+ this.log.debug(`Page readiness check timed out after ${actualTimeoutMs}ms`);
159
+ } else {
160
+ this.log.warn(`Page readiness check has failed. Original error: ${err.message}`);
156
161
  }
157
- this.log.debug(`Page readiness check timed out after ${actualTimeoutMs}ms`);
158
162
  return false;
159
163
  }
160
164
  }
@@ -10,6 +10,9 @@ import AsyncLock from 'async-lock';
10
10
 
11
11
  const DATA_LOG_LENGTH = {length: 200};
12
12
  const MIN_WAIT_FOR_TARGET_TIMEOUT_MS = 30000;
13
+ // Sometimes page initialization can take a long time, especially in slow CI environments,
14
+ // although we still should not allow it to take forever
15
+ const PAGE_INIT_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
13
16
  const WAIT_FOR_TARGET_INTERVAL_MS = 100;
14
17
  const MIN_PLATFORM_FOR_TARGET_BASED = '12.2';
15
18
  // `Target.exists` protocol method was removed from WebKit in 13.4
@@ -409,7 +412,7 @@ export class RpcClient {
409
412
  messageHandled = false;
410
413
  // do not log receipts
411
414
  // @ts-ignore messageHandler must be defined
412
- this.messageHandler.once(msgId.toString(), function (err) {
415
+ this.messageHandler.once(msgId.toString(), (err) => {
413
416
  if (err) {
414
417
  // we are not waiting for this, and if it errors it is most likely
415
418
  // a protocol change. Log and check during testing
@@ -426,7 +429,7 @@ export class RpcClient {
426
429
  // @ts-ignore messageHandler must be defined
427
430
  } else if (this.messageHandler.listeners(cmd.__selector).length) {
428
431
  // @ts-ignore messageHandler must be defined
429
- this.messageHandler.prependOnceListener(cmd.__selector, function (err, ...args) {
432
+ this.messageHandler.prependOnceListener(cmd.__selector, (err, ...args) => {
430
433
  if (err) {
431
434
  return reject(err);
432
435
  }
@@ -436,7 +439,7 @@ export class RpcClient {
436
439
  });
437
440
  } else if (hasSocketData) {
438
441
  // @ts-ignore messageHandler must be defined
439
- this.messageHandler.once(msgId.toString(), function (err, value) {
442
+ this.messageHandler.once(msgId.toString(), (err, value) => {
440
443
  if (err) {
441
444
  return reject(new Error(`Remote debugger error with code '${err.code}': ${err.message}`));
442
445
  }
@@ -521,52 +524,74 @@ export class RpcClient {
521
524
  return;
522
525
  }
523
526
 
527
+ const pageInitTimeoutMs = Math.max(this.pageLoadTimeoutMs ?? 0, PAGE_INIT_TIMEOUT_MS);
528
+ if (!_.isPlainObject(this.targets[appIdKey])) {
529
+ this.targets[appIdKey] = {
530
+ lock: new AsyncLock({maxOccupationTime: pageInitTimeoutMs}),
531
+ };
532
+ }
533
+ const timer = new timing.Timer().start();
534
+
524
535
  if (targetInfo.isProvisional) {
525
536
  log.debug(
526
- `Provisional target created for app '${appIdKey}' and page '${pageIdKey}': '${targetInfo.targetId}'`
537
+ `Provisional target created for app '${appIdKey}' and page '${pageIdKey}': '${JSON.stringify(targetInfo)}'`
527
538
  );
528
- if (!targetInfo.isPaused) {
529
- return;
530
- }
531
539
 
532
- log.debug(`The target ${targetInfo.targetId}@${appIdKey} is paused`);
533
- const appTargetsMap = this.targets[appIdKey];
534
- if (appTargetsMap) {
535
- await appTargetsMap.lock.acquire(toLockKey(appIdKey, pageIdKey), async () => {
540
+ try {
541
+ await this.targets[appIdKey].lock.acquire(targetInfo.targetId, async () => {
536
542
  try {
537
543
  await this._initializePage(appIdKey, pageIdKey, targetInfo.targetId);
538
544
  } finally {
539
- await this._resumeTarget(appIdKey, pageIdKey, targetInfo.targetId);
545
+ if (targetInfo.isPaused) {
546
+ await this._resumeTarget(appIdKey, pageIdKey, targetInfo.targetId);
547
+ } else {
548
+ log.debug(
549
+ `Provisional target ${targetInfo.targetId}@${appIdKey} is not paused. This might be a problem`
550
+ );
551
+ }
540
552
  }
541
553
  });
554
+ } catch (e) {
555
+ log.warn(
556
+ `Cannot complete the initialization of the provisional target '${targetInfo.targetId}' ` +
557
+ `after ${timer.getDuration().asMilliSeconds}ms: ${e.message}`
558
+ );
542
559
  }
543
560
  return;
544
561
  }
545
562
 
546
563
  log.debug(`Target created for app '${appIdKey}' and page '${pageIdKey}': ${JSON.stringify(targetInfo)}`);
547
564
  if (_.has(this.targets[appIdKey], pageIdKey)) {
548
- log.debug(`There is already a target for this app and page ('${this.targets[appIdKey][pageIdKey]}'). This might cause problems`);
565
+ log.debug(
566
+ `There is already a target for this app and page ('${this.targets[appIdKey][pageIdKey]}'). ` +
567
+ `This might cause problems`
568
+ );
549
569
  }
550
- const lock = new AsyncLock();
551
- this.targets[app] = this.targets[app] || { lock };
552
570
  this.targets[appIdKey][pageIdKey] = targetInfo.targetId;
553
571
 
554
- await lock.acquire(toLockKey(appIdKey, pageIdKey), async () => {
555
- try {
556
- await this.send('Target.setPauseOnStart', {
557
- pauseOnStart: true,
558
- appIdKey,
559
- pageIdKey,
560
- });
561
- } catch {}
562
- try {
563
- await this._initializePage(appIdKey, pageIdKey);
564
- } finally {
565
- if (targetInfo.isPaused) {
566
- await this._resumeTarget(appIdKey, pageIdKey);
572
+ try {
573
+ await this.targets[appIdKey].lock.acquire(targetInfo.targetId, async () => {
574
+ try {
575
+ await this.send('Target.setPauseOnStart', {
576
+ pauseOnStart: true,
577
+ appIdKey,
578
+ pageIdKey,
579
+ });
580
+ } catch {}
581
+ try {
582
+ await this._initializePage(appIdKey, pageIdKey, targetInfo.targetId);
583
+ } finally {
584
+ if (targetInfo.isPaused) {
585
+ await this._resumeTarget(appIdKey, pageIdKey, targetInfo.targetId);
586
+ }
567
587
  }
568
- }
569
- });
588
+ });
589
+ } catch (e) {
590
+ log.warn(
591
+ `Cannot complete the initialization of the new target '${targetInfo.targetId}' ` +
592
+ `after ${timer.getDuration().asMilliSeconds}ms: ${e.message}`
593
+ );
594
+ }
570
595
  }
571
596
 
572
597
  /**
@@ -656,7 +681,7 @@ export class RpcClient {
656
681
  if (!appIdKey || !pageIdKey) {
657
682
  return;
658
683
  }
659
- return (this.targets[appIdKey] || {})[pageIdKey];
684
+ return this.targets[appIdKey]?.[pageIdKey];
660
685
  }
661
686
 
662
687
  /**
@@ -715,6 +740,7 @@ export class RpcClient {
715
740
  };
716
741
 
717
742
  log.debug(`Initializing page '${pageIdKey}' for app '${appIdKey}'`);
743
+ const timer = new timing.Timer().start();
718
744
  if (!this.fullPageInitialization) {
719
745
  // The sequence of domains is important
720
746
  for (const domain of [
@@ -735,7 +761,10 @@ export class RpcClient {
735
761
  log.info(`Cannot enable domain '${domain}' during initialization: ${err.message}`);
736
762
  }
737
763
  }
738
- log.debug(`Simple initialization of page '${pageIdKey}' for app '${appIdKey}' completed`);
764
+ log.debug(
765
+ `Simple initialization of page '${pageIdKey}' for app '${appIdKey}' completed ` +
766
+ `in ${timer.getDuration().asMilliSeconds}ms`
767
+ );
739
768
  return;
740
769
  }
741
770
 
@@ -817,7 +846,10 @@ export class RpcClient {
817
846
  log.info(`Cannot enable domain '${domain}' during full initialization: ${err.message}`);
818
847
  }
819
848
  }
820
- log.debug(`Full initialization of page '${pageIdKey}' for app '${appIdKey}' completed`);
849
+ log.debug(
850
+ `Full initialization of page '${pageIdKey}' for app '${appIdKey}' completed ` +
851
+ `in ${timer.getDuration().asMilliSeconds}ms`
852
+ );
821
853
  }
822
854
 
823
855
  /**
@@ -904,7 +936,7 @@ export class RpcClient {
904
936
  *
905
937
  * @param {import('../types').AppIdKey} appIdKey
906
938
  * @param {import('../types').PageIdKey} pageIdKey
907
- * @param {import('../types').TargetId} [targetId]
939
+ * @param {import('../types').TargetId} targetId
908
940
  * @returns {Promise<void>}
909
941
  */
910
942
  async _resumeTarget (appIdKey, pageIdKey, targetId) {
@@ -915,7 +947,9 @@ export class RpcClient {
915
947
  targetId,
916
948
  });
917
949
  log.debug(`Successfully resumed the target ${targetId}@${appIdKey}`);
918
- } catch {}
950
+ } catch (e) {
951
+ log.warn(`Could not resume the target ${targetId}@${appIdKey}: ${e.message}`);
952
+ }
919
953
  }
920
954
 
921
955
  /**
@@ -928,29 +962,16 @@ export class RpcClient {
928
962
  if (!appTargetsMap) {
929
963
  throw new Error(`No targets found for app '${appIdKey}'`);
930
964
  }
931
- /** @type {AsyncLock | undefined} */
932
965
  const lock = appTargetsMap.lock;
933
- if (lock?.isBusy()) {
934
- const timer = new timing.Timer().start();
935
- log.debug(`Waiting until page ${pageIdKey}@${appIdKey} is fully initialized`);
936
- await lock.acquire(toLockKey(appIdKey, pageIdKey), async () => await B.delay(0));
937
- log.debug(
938
- `The initalization of the page ${pageIdKey}@${appIdKey} took ${timer.getDuration().asMilliSeconds}ms`
939
- );
966
+ const timer = new timing.Timer().start();
967
+ await lock.acquire(appTargetsMap[pageIdKey], async () => await B.delay(0));
968
+ const durationMs = timer.getDuration().asMilliSeconds;
969
+ if (durationMs > 10) {
970
+ log.debug(`Waited ${durationMs}ms until the page ${pageIdKey}@${appIdKey} is initialized`);
940
971
  }
941
972
  }
942
973
  }
943
974
 
944
- /**
945
- *
946
- * @param {import('../types').AppIdKey} appIdKey
947
- * @param {import('../types').PageIdKey} pageIdKey
948
- * @returns {string}
949
- */
950
- export function toLockKey(appIdKey, pageIdKey) {
951
- return `${appIdKey}-${pageIdKey}`;
952
- }
953
-
954
975
  export default RpcClient;
955
976
 
956
977
  /**
@@ -972,6 +993,6 @@ export default RpcClient;
972
993
  */
973
994
 
974
995
  /**
975
- * @typedef {PageDict & {provisional?: import('../types').ProvisionalTargetInfo, lock: import('async-lock').default}} PagesToTargets
996
+ * @typedef {PageDict & {provisional?: import('../types').ProvisionalTargetInfo, lock: AsyncLock}} PagesToTargets
976
997
  * @typedef {{[key: import('../types').AppIdKey]: PagesToTargets}} AppToTargetsMap
977
998
  */
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "keywords": [
5
5
  "appium"
6
6
  ],
7
- "version": "14.0.2",
7
+ "version": "14.0.4",
8
8
  "author": "Appium Contributors",
9
9
  "license": "Apache-2.0",
10
10
  "repository": {