appium-remote-debugger 15.2.11 → 15.2.13

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/lib/atoms.ts ADDED
@@ -0,0 +1,98 @@
1
+ import { fs } from '@appium/support';
2
+ import path from 'path';
3
+ import _ from 'lodash';
4
+ import { log } from './logger';
5
+ import { getModuleRoot } from './utils';
6
+
7
+ const ATOMS_CACHE: Record<string, Buffer> = {};
8
+
9
+ /**
10
+ * Converts a value to a JSON string, handling undefined values specially.
11
+ *
12
+ * @param obj - The value to stringify.
13
+ * @returns A JSON string representation of the value, or 'undefined' if the value is undefined.
14
+ */
15
+ function atomsStringify(obj: any): string {
16
+ if (typeof obj === 'undefined') {
17
+ return 'undefined';
18
+ }
19
+ return JSON.stringify(obj);
20
+ }
21
+
22
+ /**
23
+ * Loads an atom script from the atoms directory and caches it.
24
+ * If the atom has been loaded before, returns the cached version.
25
+ *
26
+ * @param atomName - The name of the atom to load (without the .js extension).
27
+ * @returns A promise that resolves to the atom script as a Buffer.
28
+ * @throws Error if the atom file cannot be loaded.
29
+ */
30
+ export async function getAtom(atomName: string): Promise<Buffer> {
31
+ // check if we have already loaded and cached this atom
32
+ if (!_.has(ATOMS_CACHE, atomName)) {
33
+ const atomFileName = path.resolve(getModuleRoot(), 'atoms', `${atomName}.js`);
34
+ try {
35
+ ATOMS_CACHE[atomName] = await fs.readFile(atomFileName);
36
+ } catch {
37
+ throw new Error(`Unable to load Atom '${atomName}' from file '${atomFileName}'`);
38
+ }
39
+ }
40
+
41
+ return ATOMS_CACHE[atomName];
42
+ }
43
+
44
+ /**
45
+ * Wraps a script to execute it within a specific frame context.
46
+ * Uses the get_element_from_cache atom to access the frame element.
47
+ *
48
+ * @param script - The script to wrap.
49
+ * @param frame - The frame identifier to execute the script in.
50
+ * @returns A promise that resolves to the wrapped script string.
51
+ */
52
+ async function wrapScriptForFrame(script: string, frame: string): Promise<string> {
53
+ log.debug(`Wrapping script for frame '${frame}'`);
54
+ const elFromCache = await getAtom('get_element_from_cache');
55
+ return `(function (window) { var document = window.document; ` +
56
+ `return (${script}); })((${elFromCache.toString('utf8')})(${atomsStringify(frame)}))`;
57
+ }
58
+
59
+ /**
60
+ * Generates a complete script string for executing a Selenium atom.
61
+ * Handles frame contexts and optional async callbacks.
62
+ *
63
+ * @param atom - The name of the atom to execute.
64
+ * @param args - Arguments to pass to the atom function. Defaults to empty array.
65
+ * @param frames - Array of frame identifiers to execute the atom in nested frames.
66
+ * Defaults to empty array (executes in default context).
67
+ * @param asyncCallBack - Optional callback function string for async execution.
68
+ * If provided, the atom will be called with this callback.
69
+ * @returns A promise that resolves to the complete script string ready for execution.
70
+ */
71
+ export async function getScriptForAtom(
72
+ atom: string,
73
+ args: any[] = [],
74
+ frames: string[] = [],
75
+ asyncCallBack: string | null = null
76
+ ): Promise<string> {
77
+ const atomSrc = (await getAtom(atom)).toString('utf8');
78
+ let script: string;
79
+ if (frames.length > 0) {
80
+ script = atomSrc;
81
+ for (const frame of frames) {
82
+ script = await wrapScriptForFrame(script, frame);
83
+ }
84
+ } else {
85
+ log.debug(`Executing '${atom}' atom in default context`);
86
+ script = `(${atomSrc})`;
87
+ }
88
+
89
+ // add the arguments, as strings
90
+ args = args.map(atomsStringify);
91
+ if (asyncCallBack) {
92
+ script += `(${args.join(',')}, ${asyncCallBack}, true)`;
93
+ } else {
94
+ script += `(${args.join(',')})`;
95
+ }
96
+
97
+ return script;
98
+ }
package/lib/logger.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { logger } from '@appium/support';
2
+
3
+ export const log = logger.getLogger('RemoteDebugger');
@@ -1,5 +1,5 @@
1
1
  import { EventEmitter } from 'events';
2
- import defaultLog from './logger';
2
+ import { log as defaultLog } from './logger';
3
3
  import { RpcClientSimulator } from './rpc';
4
4
  import { getModuleProperties } from './utils';
5
5
  import * as connectMixins from './mixins/connect';
@@ -1,4 +1,4 @@
1
- import log from '../logger';
1
+ import { log } from '../logger';
2
2
  import { RpcClient } from './rpc-client';
3
3
  import { services } from 'appium-ios-device';
4
4
 
@@ -1,4 +1,4 @@
1
- import log from '../logger';
1
+ import { log } from '../logger';
2
2
  import _ from 'lodash';
3
3
  import B from 'bluebird';
4
4
  import net from 'net';
@@ -1,6 +1,6 @@
1
1
  import { RemoteMessages } from './remote-messages';
2
2
  import { waitForCondition } from 'asyncbox';
3
- import log from '../logger';
3
+ import { log } from '../logger';
4
4
  import _ from 'lodash';
5
5
  import B from 'bluebird';
6
6
  import RpcMessageHandler from './rpc-message-handler';
@@ -1,4 +1,4 @@
1
- import log from '../logger';
1
+ import { log } from '../logger';
2
2
  import _ from 'lodash';
3
3
  import { util } from '@appium/support';
4
4
  import EventEmitters from 'events';
@@ -3,6 +3,8 @@ import { errorFromMJSONWPStatusCode } from '@appium/base-driver';
3
3
  import { util, node } from '@appium/support';
4
4
  import nodeFs from 'node:fs';
5
5
  import path from 'node:path';
6
+ import type { StringRecord } from '@appium/types';
7
+ import type { AppInfo, AppDict, Page } from './types';
6
8
 
7
9
  const MODULE_NAME = 'appium-remote-debugger';
8
10
  export const WEB_CONTENT_BUNDLE_ID = 'com.apple.WebKit.WebContent';
@@ -16,13 +18,13 @@ const ACCEPTED_PAGE_TYPES = [
16
18
  export const RESPONSE_LOG_LENGTH = 100;
17
19
 
18
20
  /**
19
- * Takes a dictionary from the remote debugger and makes a more manageable
20
- * dictionary whose keys are understandable
21
+ * Takes a dictionary from the remote debugger and converts it into a more
22
+ * manageable AppInfo object with understandable keys.
21
23
  *
22
- * @param {Record<string, any>} dict
23
- * @returns {[string, import('./types').AppInfo]}
24
+ * @param dict - Dictionary from the remote debugger containing application information.
25
+ * @returns A tuple containing the application ID and the AppInfo object.
24
26
  */
25
- export function appInfoFromDict (dict) {
27
+ export function appInfoFromDict(dict: Record<string, any>): [string, AppInfo] {
26
28
  const id = dict.WIRApplicationIdentifierKey;
27
29
  const isProxy = _.isString(dict.WIRIsApplicationProxyKey)
28
30
  ? dict.WIRIsApplicationProxyKey.toLowerCase() === 'true'
@@ -30,8 +32,7 @@ export function appInfoFromDict (dict) {
30
32
  // automation enabled can be either from the keys
31
33
  // - WIRRemoteAutomationEnabledKey (boolean)
32
34
  // - WIRAutomationAvailabilityKey (string or boolean)
33
- /** @type {boolean|string} */
34
- let isAutomationEnabled = !!dict.WIRRemoteAutomationEnabledKey;
35
+ let isAutomationEnabled: boolean | string = !!dict.WIRRemoteAutomationEnabledKey;
35
36
  if (_.has(dict, 'WIRAutomationAvailabilityKey')) {
36
37
  if (_.isString(dict.WIRAutomationAvailabilityKey)) {
37
38
  isAutomationEnabled = dict.WIRAutomationAvailabilityKey === 'WIRAutomationAvailabilityUnknown'
@@ -41,8 +42,7 @@ export function appInfoFromDict (dict) {
41
42
  isAutomationEnabled = !!dict.WIRAutomationAvailabilityKey;
42
43
  }
43
44
  }
44
- /** @type {import('./types').AppInfo} */
45
- const entry = {
45
+ const entry: AppInfo = {
46
46
  id,
47
47
  isProxy,
48
48
  name: dict.WIRApplicationNameKey,
@@ -56,13 +56,13 @@ export function appInfoFromDict (dict) {
56
56
  }
57
57
 
58
58
  /**
59
- * Take a dictionary from the remote debugger and makes a more manageable
60
- * dictionary of pages available.
59
+ * Takes a dictionary from the remote debugger and converts it into an array
60
+ * of Page objects with understandable keys. Filters out non-web pages.
61
61
  *
62
- * @param {import('@appium/types').StringRecord} pageDict
63
- * @returns {import('./types').Page[]}
62
+ * @param pageDict - Dictionary from the remote debugger containing page information.
63
+ * @returns An array of Page objects representing the available pages.
64
64
  */
65
- export function pageArrayFromDict (pageDict) {
65
+ export function pageArrayFromDict(pageDict: StringRecord): Page[] {
66
66
  return _.values(pageDict)
67
67
  // count only WIRTypeWeb pages and ignore all others (WIRTypeJavaScript etc)
68
68
  .filter((dict) => _.isUndefined(dict.WIRTypeKey) || ACCEPTED_PAGE_TYPES.includes(dict.WIRTypeKey))
@@ -75,14 +75,16 @@ export function pageArrayFromDict (pageDict) {
75
75
  }
76
76
 
77
77
  /**
78
+ * Finds all application identifier keys that match the given bundle ID.
79
+ * If no matches are found and the bundle ID is not WEB_CONTENT_BUNDLE_ID,
80
+ * falls back to searching for WEB_CONTENT_BUNDLE_ID.
78
81
  *
79
- * @param {string} bundleId
80
- * @param {import('./types').AppDict} appDict
81
- * @returns {string[]}
82
+ * @param bundleId - The bundle identifier to search for.
83
+ * @param appDict - The application dictionary to search in.
84
+ * @returns An array of unique application identifier keys matching the bundle ID.
82
85
  */
83
- export function appIdsForBundle (bundleId, appDict) {
84
- /** @type {string[]} */
85
- const appIds = _.toPairs(appDict)
86
+ export function appIdsForBundle(bundleId: string, appDict: AppDict): string[] {
87
+ const appIds: string[] = _.toPairs(appDict)
86
88
  .filter(([, data]) => data.bundleId === bundleId)
87
89
  .map(([key]) => key);
88
90
 
@@ -95,11 +97,15 @@ export function appIdsForBundle (bundleId, appDict) {
95
97
  }
96
98
 
97
99
  /**
98
- * @template {import('@appium/types').StringRecord} T
99
- * @param {T} params
100
- * @returns {T}
100
+ * Validates that all parameters in the provided object have non-nil values.
101
+ * Throws an error if any parameters are missing (null or undefined).
102
+ *
103
+ * @template T - The type of the parameters object.
104
+ * @param params - An object containing parameters to validate.
105
+ * @returns The same parameters object if all values are valid.
106
+ * @throws Error if any parameters are missing, listing all missing parameter names.
101
107
  */
102
- export function checkParams (params) {
108
+ export function checkParams<T extends StringRecord>(params: T): T {
103
109
  // check if all parameters have a value
104
110
  const errors = _.toPairs(params)
105
111
  .filter(([, value]) => _.isNil(value))
@@ -111,30 +117,33 @@ export function checkParams (params) {
111
117
  }
112
118
 
113
119
  /**
114
- * @param {any} value
115
- * @param {boolean} [multiline=false]
116
- * @returns {string}
120
+ * Converts a value to a JSON string, removing noisy function properties
121
+ * that can muddy the logs.
122
+ *
123
+ * @param value - The value to stringify.
124
+ * @param multiline - If true, formats the JSON with indentation. Defaults to false.
125
+ * @returns A JSON string representation of the value with noisy properties removed.
117
126
  */
118
- export function simpleStringify (value, multiline = false) {
127
+ export function simpleStringify(value: any, multiline: boolean = false): string {
119
128
  if (!value) {
120
129
  return JSON.stringify(value);
121
130
  }
122
131
 
123
- // we get back objects sometimes with string versions of functions
124
- // which muddy the logs
125
- let cleanValue = _.clone(value);
126
- for (const property of ['ceil', 'clone', 'floor', 'round', 'scale', 'toString']) {
127
- delete cleanValue[property];
128
- }
132
+ const cleanValue = removeNoisyProperties(_.clone(value));
129
133
  return multiline ? JSON.stringify(cleanValue, null, 2) : JSON.stringify(cleanValue);
130
134
  }
131
135
 
132
136
  /**
137
+ * Converts the result from a JavaScript evaluation in the remote debugger
138
+ * into a usable format. Handles errors, serialization, and cleans up noisy
139
+ * function properties.
133
140
  *
134
- * @param {any} res
135
- * @returns {any}
141
+ * @param res - The raw result from the remote debugger's JavaScript evaluation.
142
+ * @returns The cleaned and converted result value.
143
+ * @throws Error if the result is undefined, has an unexpected type, or contains
144
+ * an error status code.
136
145
  */
137
- export function convertJavascriptEvaluationResult (res) {
146
+ export function convertJavascriptEvaluationResult(res: any): any {
138
147
  if (_.isUndefined(res)) {
139
148
  throw new Error(`Did not get OK result from remote debugger. Result was: ${_.truncate(simpleStringify(res), {length: RESPONSE_LOG_LENGTH})}`);
140
149
  } else if (_.isString(res)) {
@@ -156,23 +165,17 @@ export function convertJavascriptEvaluationResult (res) {
156
165
  // with either have an object with a `value` property (even if `null`),
157
166
  // or a plain object
158
167
  const value = _.has(res, 'value') ? res.value : res;
159
-
160
- // get rid of noisy functions on objects
161
- if (_.isObject(value)) {
162
- for (const property of ['ceil', 'clone', 'floor', 'round', 'scale', 'toString']) {
163
- delete value[property];
164
- }
165
- }
166
- return value;
168
+ return removeNoisyProperties(value);
167
169
  }
168
170
 
169
171
  /**
170
- * Calculates the path to the current module's root folder
172
+ * Calculates the path to the current module's root folder.
173
+ * The result is memoized for performance.
171
174
  *
172
- * @returns {string} The full path to module root
173
- * @throws {Error} If the current module root folder cannot be determined
175
+ * @returns The full path to the module root directory.
176
+ * @throws Error if the module root folder cannot be determined.
174
177
  */
175
- export const getModuleRoot = _.memoize(function getModuleRoot () {
178
+ export const getModuleRoot = _.memoize(function getModuleRoot(): string {
176
179
  const root = node.getModuleRootSync(MODULE_NAME, __filename);
177
180
  if (!root) {
178
181
  throw new Error(`Cannot find the root folder of the ${MODULE_NAME} Node.js module`);
@@ -181,9 +184,27 @@ export const getModuleRoot = _.memoize(function getModuleRoot () {
181
184
  });
182
185
 
183
186
  /**
184
- * @returns {import('@appium/types').StringRecord}
187
+ * Reads and parses the package.json file from the module root.
188
+ *
189
+ * @returns The parsed package.json contents as a StringRecord.
185
190
  */
186
- export function getModuleProperties() {
191
+ export function getModuleProperties(): StringRecord {
187
192
  const fullPath = path.resolve(getModuleRoot(), 'package.json');
188
193
  return JSON.parse(nodeFs.readFileSync(fullPath, 'utf8'));
189
194
  }
195
+
196
+ /**
197
+ * Removes noisy function properties from an object that can muddy the logs.
198
+ * These properties are often added by JavaScript number objects and similar.
199
+ *
200
+ * @param obj - The object to clean.
201
+ * @returns The cleaned object.
202
+ */
203
+ function removeNoisyProperties<T>(obj: T): T {
204
+ if (_.isObject(obj)) {
205
+ for (const property of ['ceil', 'clone', 'floor', 'round', 'scale', 'toString']) {
206
+ delete obj[property];
207
+ }
208
+ }
209
+ return obj;
210
+ }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "keywords": [
5
5
  "appium"
6
6
  ],
7
- "version": "15.2.11",
7
+ "version": "15.2.13",
8
8
  "author": "Appium Contributors",
9
9
  "license": "Apache-2.0",
10
10
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "@appium/base-driver": "^10.0.0-rc.1",
37
37
  "@appium/support": "^7.0.0-rc.1",
38
38
  "appium-ios-device": "^3.0.0",
39
- "asyncbox": "^4.0.1",
39
+ "asyncbox": "^5.0.0",
40
40
  "async-lock": "^1.4.1",
41
41
  "bluebird": "^3.4.7",
42
42
  "glob": "^13.0.0",
package/lib/atoms.js DELETED
@@ -1,84 +0,0 @@
1
- import { fs } from '@appium/support';
2
- import path from 'path';
3
- import _ from 'lodash';
4
- import log from './logger';
5
- import { getModuleRoot } from './utils';
6
-
7
- const ATOMS_CACHE = {};
8
-
9
- /**
10
- * @param {any} obj
11
- * @returns {string}
12
- */
13
- function atomsStringify(obj) {
14
- if (typeof obj === 'undefined') {
15
- return 'undefined';
16
- }
17
- return JSON.stringify(obj);
18
- }
19
-
20
- /**
21
- *
22
- * @param {string} atomName
23
- * @returns {Promise<Buffer>}
24
- */
25
- async function getAtom (atomName) {
26
- // check if we have already loaded and cached this atom
27
- if (!_.has(ATOMS_CACHE, atomName)) {
28
- const atomFileName = path.resolve(getModuleRoot(), 'atoms', `${atomName}.js`);
29
- try {
30
- ATOMS_CACHE[atomName] = await fs.readFile(atomFileName);
31
- } catch {
32
- throw new Error(`Unable to load Atom '${atomName}' from file '${atomFileName}'`);
33
- }
34
- }
35
-
36
- return ATOMS_CACHE[atomName];
37
- }
38
-
39
- /**
40
- * @param {string} script
41
- * @param {string} frame
42
- * @returns {Promise<string>}
43
- */
44
- async function wrapScriptForFrame (script, frame) {
45
- log.debug(`Wrapping script for frame '${frame}'`);
46
- const elFromCache = await getAtom('get_element_from_cache');
47
- return `(function (window) { var document = window.document; ` +
48
- `return (${script}); })((${elFromCache.toString('utf8')})(${atomsStringify(frame)}))`;
49
- }
50
-
51
- /**
52
- *
53
- * @param {string} atom
54
- * @param {any[]} [args]
55
- * @param {string[]} [frames]
56
- * @param {string?} [asyncCallBack]
57
- * @returns {Promise<string>}
58
- */
59
- async function getScriptForAtom (atom, args = [], frames = [], asyncCallBack = null) {
60
- const atomSrc = (await getAtom(atom)).toString('utf8');
61
- let script;
62
- if (frames.length > 0) {
63
- script = atomSrc;
64
- for (const frame of frames) {
65
- script = await wrapScriptForFrame(script, frame);
66
- }
67
- } else {
68
- log.debug(`Executing '${atom}' atom in default context`);
69
- script = `(${atomSrc})`;
70
- }
71
-
72
- // add the arguments, as strings
73
- args = args.map(atomsStringify);
74
- if (asyncCallBack) {
75
- script += `(${args.join(',')}, ${asyncCallBack}, true)`;
76
- } else {
77
- script += `(${args.join(',')})`;
78
- }
79
-
80
- return script;
81
- }
82
-
83
- export { getAtom, getScriptForAtom };
84
- export default getAtom;
package/lib/logger.js DELETED
@@ -1,6 +0,0 @@
1
- import { logger } from '@appium/support';
2
-
3
-
4
- const log = logger.getLogger('RemoteDebugger');
5
-
6
- export default log;