appium-remote-debugger 15.2.9 → 15.2.10

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.
@@ -13,6 +13,8 @@ import {
13
13
  getPageIdKey,
14
14
  setNavigatingToPage,
15
15
  } from './property-accessors';
16
+ import type { RemoteDebugger } from '../remote-debugger';
17
+ import type { AppIdKey, PageIdKey } from '../types';
16
18
 
17
19
  export const DEFAULT_PAGE_READINESS_TIMEOUT_MS = 20 * 1000;
18
20
  const PAGE_READINESS_CHECK_INTERVAL_MS = 50;
@@ -27,32 +29,34 @@ const PAGE_LOAD_STRATEGY = Object.freeze({
27
29
  });
28
30
 
29
31
  /**
30
- * @this {RemoteDebugger}
31
- * @returns {void}
32
+ * Emits a frame detached event when a frame is detached from the page.
33
+ * This is typically called by the RPC client when receiving a Page.frameDetached event.
32
34
  */
33
- export function frameDetached () {
35
+ export function frameDetached(this: RemoteDebugger): void {
34
36
  this.emit(events.EVENT_FRAMES_DETACHED);
35
37
  }
36
38
 
37
39
  /**
38
- * @this {RemoteDebugger}
39
- * @returns {void}
40
+ * Cancels the current page load operation by unregistering from page readiness
41
+ * notifications and canceling any pending page load delay.
40
42
  */
41
- export function cancelPageLoad () {
43
+ export function cancelPageLoad(this: RemoteDebugger): void {
42
44
  this.log.debug('Unregistering from page readiness notifications');
43
45
  setPageLoading(this, false);
44
46
  getPageLoadDelay(this)?.cancel();
45
47
  }
46
48
 
47
49
  /**
48
- * Return if current readState can be handles as page load completes
49
- * for the given page load strategy.
50
+ * Determines if the current readyState indicates that page loading is completed
51
+ * based on the configured page load strategy.
50
52
  *
51
- * @this {RemoteDebugger}
52
- * @param {string} readyState
53
- * @returns {boolean}
53
+ * @param readyState - The document readyState value ('loading', 'interactive', or 'complete').
54
+ * @returns True if the page load is considered complete for the current strategy:
55
+ * - 'eager': returns true when readyState is not 'loading'
56
+ * - 'none': always returns true
57
+ * - 'normal' (default): returns true only when readyState is 'complete'
54
58
  */
55
- export function isPageLoadingCompleted (readyState) {
59
+ export function isPageLoadingCompleted(this: RemoteDebugger, readyState: string): boolean {
56
60
  const pageLoadStrategy = _.toLower(getPageLoadStartegy(this));
57
61
  switch (pageLoadStrategy) {
58
62
  case PAGE_LOAD_STRATEGY.EAGER:
@@ -67,11 +71,14 @@ export function isPageLoadingCompleted (readyState) {
67
71
  }
68
72
 
69
73
  /**
70
- * @this {RemoteDebugger}
71
- * @param {timing.Timer?} [startPageLoadTimer]
72
- * @returns {Promise<void>}
74
+ * Waits for the DOM to be ready by periodically checking the page readiness state.
75
+ * Uses exponential backoff for retry intervals and respects the configured page load
76
+ * strategy and timeout settings.
77
+ *
78
+ * @param startPageLoadTimer - Optional timer instance to use for tracking elapsed time.
79
+ * If not provided, a new timer will be created and started.
73
80
  */
74
- export async function waitForDom (startPageLoadTimer) {
81
+ export async function waitForDom(this: RemoteDebugger, startPageLoadTimer?: timing.Timer): Promise<void> {
75
82
  const readinessTimeoutMs = this.pageLoadMs;
76
83
  this.log.debug(`Waiting up to ${readinessTimeoutMs}ms for the page to be ready`);
77
84
  const timer = startPageLoadTimer ?? new timing.Timer().start();
@@ -79,7 +86,6 @@ export async function waitForDom (startPageLoadTimer) {
79
86
  let isPageLoading = true;
80
87
  setPageLoading(this, true);
81
88
  setPageLoadDelay(this, util.cancellableDelay(readinessTimeoutMs));
82
- /** @type {B<void>} */
83
89
  const pageReadinessPromise = B.resolve((async () => {
84
90
  let retry = 0;
85
91
  while (isPageLoading) {
@@ -118,7 +124,6 @@ export async function waitForDom (startPageLoadTimer) {
118
124
  retry++;
119
125
  }
120
126
  })());
121
- /** @type {B<void>} */
122
127
  const cancellationPromise = B.resolve((async () => {
123
128
  try {
124
129
  await getPageLoadDelay(this);
@@ -135,11 +140,15 @@ export async function waitForDom (startPageLoadTimer) {
135
140
  }
136
141
 
137
142
  /**
138
- * @this {RemoteDebugger}
139
- * @param {number} [timeoutMs]
140
- * @returns {Promise<boolean>}
143
+ * Checks if the current page is ready by executing a JavaScript command to
144
+ * retrieve the document readyState and evaluating it against the page load strategy.
145
+ *
146
+ * @param timeoutMs - Optional timeout in milliseconds for the readyState check.
147
+ * If not provided, uses the configured page ready timeout.
148
+ * @returns A promise that resolves to true if the page is ready according to
149
+ * the page load strategy, false otherwise or if the check times out.
141
150
  */
142
- export async function checkPageIsReady (timeoutMs) {
151
+ export async function checkPageIsReady(this: RemoteDebugger, timeoutMs?: number): Promise<boolean> {
143
152
  const readyCmd = 'document.readyState;';
144
153
  const actualTimeoutMs = timeoutMs ?? getPageReadyTimeout(this);
145
154
  try {
@@ -152,7 +161,7 @@ export async function checkPageIsReady (timeoutMs) {
152
161
  })
153
162
  );
154
163
  return this.isPageLoadingCompleted(readyState);
155
- } catch (err) {
164
+ } catch (err: any) {
156
165
  if (err instanceof BTimeoutError) {
157
166
  this.log.debug(`Page readiness check timed out after ${actualTimeoutMs}ms`);
158
167
  } else {
@@ -163,11 +172,14 @@ export async function checkPageIsReady (timeoutMs) {
163
172
  }
164
173
 
165
174
  /**
166
- * @this {RemoteDebugger}
167
- * @param {string} url
168
- * @returns {Promise<void>}
175
+ * Navigates to a new URL and waits for the page to be ready.
176
+ * Validates the URL format, waits for the page to be available, sends the navigation
177
+ * command, and monitors for the Page.loadEventFired event or timeout.
178
+ *
179
+ * @param url - The URL to navigate to. Must be a valid URL format.
180
+ * @throws TypeError if the provided URL is not a valid URL format.
169
181
  */
170
- export async function navToUrl (url) {
182
+ export async function navToUrl(this: RemoteDebugger, url: string): Promise<void> {
171
183
  const {appIdKey, pageIdKey} = checkParams({
172
184
  appIdKey: getAppIdKey(this),
173
185
  pageIdKey: getPageIdKey(this),
@@ -183,22 +195,18 @@ export async function navToUrl (url) {
183
195
  this.log.debug(`Navigating to new URL: '${url}'`);
184
196
  setNavigatingToPage(this, true);
185
197
  await rpcClient.waitForPage(
186
- /** @type {import('../types').AppIdKey} */ (appIdKey),
187
- /** @type {import('../types').PageIdKey} */ (pageIdKey)
198
+ appIdKey as AppIdKey,
199
+ pageIdKey as PageIdKey
188
200
  );
189
201
  const readinessTimeoutMs = this.pageLoadMs;
190
- /** @type {(() => void)|undefined} */
191
- let onPageLoaded;
192
- /** @type {NodeJS.Timeout|undefined|null} */
193
- let onPageLoadedTimeout;
202
+ let onPageLoaded: (() => void) | undefined;
203
+ let onPageLoadedTimeout: NodeJS.Timeout | undefined | null;
194
204
  setPageLoadDelay(this, util.cancellableDelay(readinessTimeoutMs));
195
205
  setPageLoading(this, true);
196
206
  let isPageLoading = true;
197
- // /** @type {Promise<void>|null} */
198
207
  const start = new timing.Timer().start();
199
208
 
200
- /** @type {B<void>} */
201
- const pageReadinessPromise = new B((resolve) => {
209
+ const pageReadinessPromise = new B<void>((resolve) => {
202
210
  onPageLoadedTimeout = setTimeout(() => {
203
211
  if (isPageLoading) {
204
212
  isPageLoading = false;
@@ -231,7 +239,6 @@ export async function navToUrl (url) {
231
239
  pageIdKey,
232
240
  });
233
241
  });
234
- /** @type {B<void>} */
235
242
  const cancellationPromise = B.resolve((async () => {
236
243
  try {
237
244
  await getPageLoadDelay(this);
@@ -254,7 +261,3 @@ export async function navToUrl (url) {
254
261
  }
255
262
  }
256
263
  }
257
-
258
- /**
259
- * @typedef {import('../remote-debugger').RemoteDebugger} RemoteDebugger
260
- */
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "keywords": [
5
5
  "appium"
6
6
  ],
7
- "version": "15.2.9",
7
+ "version": "15.2.10",
8
8
  "author": "Appium Contributors",
9
9
  "license": "Apache-2.0",
10
10
  "repository": {
@@ -1,224 +0,0 @@
1
- import { events } from './events';
2
- import {
3
- pageArrayFromDict,
4
- appInfoFromDict,
5
- } from '../utils';
6
- import _ from 'lodash';
7
- import {
8
- setAppIdKey,
9
- getAppDict,
10
- getAppIdKey,
11
- getBundleId,
12
- getNavigatingToPage,
13
- setCurrentState,
14
- setConnectedDrivers,
15
- getSkippedApps,
16
- } from './property-accessors';
17
-
18
- /*
19
- * Generic callbacks used throughout the lifecycle of the Remote Debugger.
20
- * These will be added to the prototype.
21
- */
22
-
23
- /**
24
- * @this {RemoteDebugger}
25
- * @param {Error?} err
26
- * @param {string} appIdKey
27
- * @param {Record<string, any>} pageDict
28
- * @returns {Promise<void>}
29
- */
30
- export async function onPageChange (err, appIdKey, pageDict) {
31
- if (_.isEmpty(pageDict)) {
32
- return;
33
- }
34
-
35
- const currentPages = pageArrayFromDict(pageDict);
36
- // save the page dict for this app
37
- if (getAppDict(this)[appIdKey]) {
38
- const previousPages = getAppDict(this)[appIdKey].pageArray;
39
- // we have a pre-existing pageDict
40
- if (previousPages && _.isEqual(previousPages, currentPages)) {
41
- this.log.debug(
42
- `Received page change notice for app '${appIdKey}' ` +
43
- `but the listing has not changed. Ignoring.`
44
- );
45
- return;
46
- }
47
- // keep track of the page dictionary
48
- getAppDict(this)[appIdKey].pageArray = currentPages;
49
- this.log.debug(
50
- `Pages changed for ${appIdKey}: ${JSON.stringify(previousPages)} -> ${JSON.stringify(currentPages)}`
51
- );
52
- }
53
-
54
- if (getNavigatingToPage(this)) {
55
- // in the middle of navigating, so reporting a page change will cause problems
56
- return;
57
- }
58
-
59
- this.emit(events.EVENT_PAGE_CHANGE, {
60
- appIdKey: appIdKey.replace('PID:', ''),
61
- pageArray: currentPages,
62
- });
63
- }
64
-
65
- /**
66
- * @this {RemoteDebugger}
67
- * @param {Error?} err
68
- * @param {Record<string, any>} dict
69
- * @returns {Promise<void>}
70
- */
71
- export async function onAppConnect (err, dict) {
72
- const appIdKey = dict.WIRApplicationIdentifierKey;
73
- this.log.debug(`Notified that new application '${appIdKey}' has connected`);
74
- updateAppsWithDict.bind(this)(dict);
75
- }
76
-
77
- /**
78
- * @this {RemoteDebugger}
79
- * @param {Error?} err
80
- * @param {import('@appium/types').StringRecord} dict
81
- * @returns {void}
82
- */
83
- export function onAppDisconnect (err, dict) {
84
- const appIdKey = dict.WIRApplicationIdentifierKey;
85
- this.log.debug(`Application '${appIdKey}' disconnected. Removing from app dictionary.`);
86
- this.log.debug(`Current app is '${getAppIdKey(this)}'`);
87
-
88
- // get rid of the entry in our app dictionary,
89
- // since it is no longer available
90
- delete getAppDict(this)[appIdKey];
91
-
92
- // if the disconnected app is the one we are connected to, try to find another
93
- if (getAppIdKey(this) === appIdKey) {
94
- this.log.debug(`No longer have app id. Attempting to find new one.`);
95
- setAppIdKey(this, getDebuggerAppKey.bind(this)(/** @type {string} */ (getBundleId(this))));
96
- }
97
-
98
- if (_.isEmpty(getAppDict(this))) {
99
- // this means we no longer have any apps. what the what?
100
- this.log.debug('Main app disconnected. Disconnecting altogether.');
101
- this.emit(events.EVENT_DISCONNECT, true);
102
- }
103
- }
104
-
105
- /**
106
- * @this {RemoteDebugger}
107
- * @param {Error?} err
108
- * @param {Record<string, any>} dict
109
- * @returns {Promise<void>}
110
- */
111
- export async function onAppUpdate (err, dict) {
112
- this.log.debug(`Notified that an application has been updated`);
113
- updateAppsWithDict.bind(this)(dict);
114
- }
115
-
116
- /**
117
- * @this {RemoteDebugger}
118
- * @param {Error?} err
119
- * @param {Record<string, any>} drivers
120
- * @returns {void}
121
- */
122
- export function onConnectedDriverList (err, drivers) {
123
- setConnectedDrivers(this, drivers.WIRDriverDictionaryKey);
124
- this.log.debug(`Received connected driver list: ${JSON.stringify(this.connectedDrivers)}`);
125
- }
126
-
127
- /**
128
- * @this {RemoteDebugger}
129
- * @param {Error?} err
130
- * @param {Record<string, any>} state
131
- * @returns {void}
132
- */
133
- export function onCurrentState (err, state) {
134
- setCurrentState(this, state.WIRAutomationAvailabilityKey);
135
- // This state changes when 'Remote Automation' in 'Settings app' > 'Safari' > 'Advanced' > 'Remote Automation' changes
136
- // WIRAutomationAvailabilityAvailable or WIRAutomationAvailabilityNotAvailable
137
- this.log.debug(`Received connected automation availability state: ${JSON.stringify(this.currentState)}`);
138
- }
139
-
140
- /**
141
- * @this {RemoteDebugger}
142
- * @param {Error?} err
143
- * @param {Record<string, any>} apps
144
- * @returns {Promise<void>}
145
- */
146
- export async function onConnectedApplicationList (err, apps) {
147
- this.log.debug(`Received connected applications list: ${_.keys(apps).join(', ')}`);
148
-
149
- // translate the received information into an easier-to-manage
150
- // hash with app id as key, and app info as value
151
- let newDict = {};
152
- for (const dict of _.values(apps)) {
153
- const [id, entry] = appInfoFromDict(dict);
154
- if (getSkippedApps(this).includes(entry.name)) {
155
- continue;
156
- }
157
- newDict[id] = entry;
158
- }
159
- // update the object's list of apps
160
- _.defaults(getAppDict(this), newDict);
161
- }
162
-
163
- /**
164
- *
165
- * @this {RemoteDebugger}
166
- * @param {import('@appium/types').StringRecord} dict
167
- * @returns {void}
168
- */
169
- function updateAppsWithDict (dict) {
170
- // get the dictionary entry into a nice form, and add it to the
171
- // application dictionary
172
- const [id, entry] = appInfoFromDict(dict);
173
- if (getAppDict(this)[id]?.pageArray) {
174
- // preserve the page dictionary for this entry
175
- entry.pageArray = getAppDict(this)[id].pageArray;
176
- }
177
- getAppDict(this)[id] = entry;
178
-
179
- // try to get the app id from our connected apps
180
- if (!getAppIdKey(this)) {
181
- setAppIdKey(this, getDebuggerAppKey.bind(this)(/** @type {string} */ (getBundleId(this))));
182
- }
183
- }
184
-
185
- /**
186
- * Given a bundle id, finds the correct remote debugger app that is
187
- * connected.
188
- *
189
- * @this {RemoteDebugger}
190
- * @param {string} bundleId
191
- * @returns {string|undefined}
192
- */
193
- export function getDebuggerAppKey (bundleId) {
194
- let appId;
195
- for (const [key, data] of _.toPairs(getAppDict(this))) {
196
- if (data.bundleId === bundleId) {
197
- appId = key;
198
- break;
199
- }
200
- }
201
- // now we need to determine if we should pick a proxy for this instead
202
- if (appId) {
203
- this.log.debug(`Found app id key '${appId}' for bundle '${bundleId}'`);
204
- let proxyAppId;
205
- for (const [key, data] of _.toPairs(getAppDict(this))) {
206
- if (data.isProxy && data.hostId === appId) {
207
- this.log.debug(`Found separate bundleId '${data.bundleId}' ` +
208
- `acting as proxy for '${bundleId}', with app id '${key}'`);
209
- // set the app id... the last one will be used, so just keep re-assigning
210
- proxyAppId = key;
211
- }
212
- }
213
- if (proxyAppId) {
214
- appId = proxyAppId;
215
- this.log.debug(`Using proxied app id '${appId}'`);
216
- }
217
- }
218
-
219
- return appId;
220
- }
221
-
222
- /**
223
- * @typedef {import('../remote-debugger').RemoteDebugger} RemoteDebugger
224
- */