appium-remote-debugger 15.7.3 → 15.8.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.
- package/CHANGELOG.md +12 -0
- package/build/lib/atoms.d.ts.map +1 -1
- package/build/lib/atoms.js +27 -28
- package/build/lib/atoms.js.map +1 -1
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +7 -0
- package/build/lib/index.js.map +1 -1
- package/build/lib/mixins/connect.d.ts.map +1 -1
- package/build/lib/mixins/connect.js +21 -25
- package/build/lib/mixins/connect.js.map +1 -1
- package/build/lib/mixins/execute.d.ts.map +1 -1
- package/build/lib/mixins/execute.js +2 -8
- package/build/lib/mixins/execute.js.map +1 -1
- package/build/lib/mixins/message-handlers.d.ts.map +1 -1
- package/build/lib/mixins/message-handlers.js +8 -12
- package/build/lib/mixins/message-handlers.js.map +1 -1
- package/build/lib/mixins/misc.d.ts.map +1 -1
- package/build/lib/mixins/misc.js +5 -39
- package/build/lib/mixins/misc.js.map +1 -1
- package/build/lib/mixins/navigate.d.ts.map +1 -1
- package/build/lib/mixins/navigate.js +20 -55
- package/build/lib/mixins/navigate.js.map +1 -1
- package/build/lib/mixins/property-accessors.d.ts +24 -0
- package/build/lib/mixins/property-accessors.d.ts.map +1 -1
- package/build/lib/mixins/property-accessors.js +24 -0
- package/build/lib/mixins/property-accessors.js.map +1 -1
- package/build/lib/remote-debugger.d.ts +38 -38
- package/build/lib/remote-debugger.d.ts.map +1 -1
- package/build/lib/remote-debugger.js +64 -69
- package/build/lib/remote-debugger.js.map +1 -1
- package/build/lib/rpc/remote-messages.d.ts.map +1 -1
- package/build/lib/rpc/remote-messages.js +7 -8
- package/build/lib/rpc/remote-messages.js.map +1 -1
- package/build/lib/rpc/rpc-client-real-device-shim.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client-real-device-shim.js +3 -6
- package/build/lib/rpc/rpc-client-real-device-shim.js.map +1 -1
- package/build/lib/rpc/rpc-client-simulator.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client-simulator.js +3 -5
- package/build/lib/rpc/rpc-client-simulator.js.map +1 -1
- package/build/lib/rpc/rpc-client.d.ts +27 -27
- package/build/lib/rpc/rpc-client.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client.js +226 -224
- package/build/lib/rpc/rpc-client.js.map +1 -1
- package/build/lib/rpc/rpc-message-handler.js +7 -10
- package/build/lib/rpc/rpc-message-handler.js.map +1 -1
- package/build/lib/types.d.ts +19 -19
- package/build/lib/types.d.ts.map +1 -1
- package/build/lib/utils.d.ts +77 -8
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +197 -29
- package/build/lib/utils.js.map +1 -1
- package/lib/atoms.ts +31 -32
- package/lib/index.ts +7 -0
- package/lib/mixins/connect.ts +22 -23
- package/lib/mixins/execute.ts +3 -5
- package/lib/mixins/message-handlers.ts +9 -10
- package/lib/mixins/misc.ts +8 -7
- package/lib/mixins/navigate.ts +58 -63
- package/lib/mixins/property-accessors.ts +24 -0
- package/lib/remote-debugger.ts +74 -76
- package/lib/rpc/remote-messages.ts +10 -5
- package/lib/rpc/rpc-client-real-device-shim.ts +3 -3
- package/lib/rpc/rpc-client-simulator.ts +3 -5
- package/lib/rpc/rpc-client.ts +259 -247
- package/lib/rpc/rpc-message-handler.ts +7 -7
- package/lib/types.ts +24 -24
- package/lib/utils.ts +205 -29
- package/package.json +3 -7
- package/scripts/common.mjs +42 -37
- package/scripts/web_inspector_proxy.mjs +3 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {EventEmitter} from 'node:events';
|
|
2
2
|
import {log} from '../logger';
|
|
3
|
-
import
|
|
3
|
+
import {isPlainObject, truncateString} from '../utils';
|
|
4
4
|
import {util} from '@appium/support';
|
|
5
5
|
import type {StringRecord} from '@appium/types';
|
|
6
6
|
|
|
@@ -106,7 +106,7 @@ export default class RpcMessageHandler extends EventEmitter {
|
|
|
106
106
|
try {
|
|
107
107
|
return JSON.parse(plist.__argument.WIRMessageDataKey.toString('utf8'));
|
|
108
108
|
} catch (err: any) {
|
|
109
|
-
log.error(`Unparseable message data: ${
|
|
109
|
+
log.error(`Unparseable message data: ${truncateString(JSON.stringify(plist), 100)}`);
|
|
110
110
|
throw new Error(`Unable to parse message data: ${err.message}`);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -132,7 +132,7 @@ export default class RpcMessageHandler extends EventEmitter {
|
|
|
132
132
|
): Promise<void> {
|
|
133
133
|
if (msgId) {
|
|
134
134
|
if (this.listenerCount(msgId)) {
|
|
135
|
-
if (
|
|
135
|
+
if (Object.hasOwn(result?.result ?? {}, 'value')) {
|
|
136
136
|
result = result.result.value;
|
|
137
137
|
}
|
|
138
138
|
this.emit(msgId, error, result);
|
|
@@ -173,12 +173,12 @@ export default class RpcMessageHandler extends EventEmitter {
|
|
|
173
173
|
break;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
if (method
|
|
176
|
+
if (method?.startsWith('Network.')) {
|
|
177
177
|
// aggregate Network events, and add original method name to the arguments
|
|
178
178
|
eventNames.push('NetworkEvent');
|
|
179
179
|
args.push(method);
|
|
180
180
|
}
|
|
181
|
-
if (method
|
|
181
|
+
if (method?.startsWith('Console.')) {
|
|
182
182
|
// aggregate Console events, and add original method name to the arguments
|
|
183
183
|
eventNames.push('ConsoleEvent');
|
|
184
184
|
args.push(method);
|
|
@@ -213,7 +213,7 @@ export default class RpcMessageHandler extends EventEmitter {
|
|
|
213
213
|
return new Error(message);
|
|
214
214
|
}
|
|
215
215
|
if (dataKey.error) {
|
|
216
|
-
if (
|
|
216
|
+
if (isPlainObject(dataKey.error)) {
|
|
217
217
|
const dataKeyError = dataKey.error as DataErrorMessage;
|
|
218
218
|
const error = new Error(defaultMessage);
|
|
219
219
|
for (const key of Object.keys(dataKeyError)) {
|
|
@@ -242,7 +242,7 @@ export default class RpcMessageHandler extends EventEmitter {
|
|
|
242
242
|
if (!dataKey.error) {
|
|
243
243
|
try {
|
|
244
244
|
const message = JSON.parse(dataKey.params.message);
|
|
245
|
-
msgId =
|
|
245
|
+
msgId = message.id === undefined ? '' : String(message.id);
|
|
246
246
|
method = message.method;
|
|
247
247
|
result = message.result || message;
|
|
248
248
|
params = result.params;
|
package/lib/types.ts
CHANGED
|
@@ -75,10 +75,6 @@ export interface RemoteDebuggerOptions {
|
|
|
75
75
|
log?: AppiumLogger;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
interface RemoteDebuggerRealDeviceSpecificOptions {
|
|
79
|
-
udid: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
78
|
export type RemoteDebuggerRealDeviceOptions = RemoteDebuggerRealDeviceSpecificOptions &
|
|
83
79
|
RemoteDebuggerOptions;
|
|
84
80
|
|
|
@@ -110,9 +106,9 @@ export interface RpcClientSimulatorOptions {
|
|
|
110
106
|
}
|
|
111
107
|
|
|
112
108
|
export type AppIdKey = string | number;
|
|
109
|
+
|
|
113
110
|
export type PageIdKey = string | number;
|
|
114
111
|
export type TargetId = string;
|
|
115
|
-
|
|
116
112
|
export interface RemoteCommandId {
|
|
117
113
|
id: string;
|
|
118
114
|
}
|
|
@@ -135,26 +131,8 @@ export interface ProtocolCommandOpts {
|
|
|
135
131
|
params: StringRecord;
|
|
136
132
|
}
|
|
137
133
|
|
|
138
|
-
type SocketDataKey = Buffer | StringRecord;
|
|
139
|
-
|
|
140
|
-
interface RemoteCommandArgument<T extends SocketDataKey> {
|
|
141
|
-
WIRSocketDataKey?: T;
|
|
142
|
-
WIRConnectionIdentifierKey?: string;
|
|
143
|
-
WIRSenderKey?: string;
|
|
144
|
-
WIRApplicationIdentifierKey?: AppIdKey;
|
|
145
|
-
WIRPageIdentifierKey?: PageIdKey;
|
|
146
|
-
WIRMessageDataTypeKey?: string;
|
|
147
|
-
WIRDestinationKey?: string;
|
|
148
|
-
WIRMessageDataKey?: string;
|
|
149
|
-
[key: string]: any;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
interface RemoteCommandTemplated<T extends SocketDataKey> {
|
|
153
|
-
__argument: RemoteCommandArgument<T>;
|
|
154
|
-
__selector: string;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
134
|
export type RawRemoteCommand = RemoteCommandTemplated<StringRecord>;
|
|
135
|
+
|
|
158
136
|
export type RemoteCommand = RemoteCommandTemplated<Buffer>;
|
|
159
137
|
|
|
160
138
|
/**
|
|
@@ -175,3 +153,25 @@ export interface ProvisionalTargetInfo {
|
|
|
175
153
|
oldTargetId: string;
|
|
176
154
|
newTargetId: string;
|
|
177
155
|
}
|
|
156
|
+
interface RemoteDebuggerRealDeviceSpecificOptions {
|
|
157
|
+
udid: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type SocketDataKey = Buffer | StringRecord;
|
|
161
|
+
|
|
162
|
+
interface RemoteCommandArgument<T extends SocketDataKey> {
|
|
163
|
+
WIRSocketDataKey?: T;
|
|
164
|
+
WIRConnectionIdentifierKey?: string;
|
|
165
|
+
WIRSenderKey?: string;
|
|
166
|
+
WIRApplicationIdentifierKey?: AppIdKey;
|
|
167
|
+
WIRPageIdentifierKey?: PageIdKey;
|
|
168
|
+
WIRMessageDataTypeKey?: string;
|
|
169
|
+
WIRDestinationKey?: string;
|
|
170
|
+
WIRMessageDataKey?: string;
|
|
171
|
+
[key: string]: any;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface RemoteCommandTemplated<T extends SocketDataKey> {
|
|
175
|
+
__argument: RemoteCommandArgument<T>;
|
|
176
|
+
__selector: string;
|
|
177
|
+
}
|
package/lib/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {errorFromMJSONWPStatusCode} from '@appium/base-driver';
|
|
3
2
|
import {util, node} from '@appium/support';
|
|
3
|
+
import {isDeepStrictEqual} from 'node:util';
|
|
4
4
|
import nodeFs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import type {StringRecord} from '@appium/types';
|
|
@@ -17,6 +17,149 @@ const ACCEPTED_PAGE_TYPES = [
|
|
|
17
17
|
];
|
|
18
18
|
export const RESPONSE_LOG_LENGTH = 100;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Error thrown when an async operation exceeds the configured timeout.
|
|
22
|
+
*/
|
|
23
|
+
export class TimeoutError extends Error {
|
|
24
|
+
constructor(message: string = 'Operation timed out') {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'TimeoutError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Truncates a string to the requested length and appends ellipsis when needed.
|
|
32
|
+
*
|
|
33
|
+
* @param value - The input string.
|
|
34
|
+
* @param length - Maximum output length.
|
|
35
|
+
* @returns The original string when short enough, otherwise a truncated variant.
|
|
36
|
+
*/
|
|
37
|
+
export function truncateString(value: string, length: number): string {
|
|
38
|
+
if (value.length <= length) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
return `${value.slice(0, Math.max(0, length - 1))}…`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a shallow object where undefined keys from `target` are filled
|
|
46
|
+
* from `defaultsObj`.
|
|
47
|
+
*
|
|
48
|
+
* @param target - The object with priority values.
|
|
49
|
+
* @param defaultsObj - The object providing fallback values.
|
|
50
|
+
* @returns A new object containing merged defaulted values.
|
|
51
|
+
*/
|
|
52
|
+
export function defaults<T extends Record<string, any>, U extends Record<string, any>>(
|
|
53
|
+
target: T,
|
|
54
|
+
defaultsObj: U,
|
|
55
|
+
): T & U {
|
|
56
|
+
const result = {...target} as T & U;
|
|
57
|
+
for (const [key, value] of Object.entries(defaultsObj)) {
|
|
58
|
+
if (result[key as keyof (T & U)] === undefined) {
|
|
59
|
+
(result as any)[key] = value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Determines whether a value is a plain object.
|
|
67
|
+
*
|
|
68
|
+
* @param value - The value to check.
|
|
69
|
+
* @returns True when the value is a non-null non-array object.
|
|
70
|
+
*/
|
|
71
|
+
export function isPlainObject(value: unknown): value is Record<string, any> {
|
|
72
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const prototype = Object.getPrototypeOf(value);
|
|
76
|
+
return prototype === Object.prototype || prototype === null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Checks whether a value should be treated as empty.
|
|
81
|
+
*
|
|
82
|
+
* @param value - The value to evaluate.
|
|
83
|
+
* @returns True for nullish values, empty arrays/strings/maps/sets, or empty objects.
|
|
84
|
+
*/
|
|
85
|
+
export function isEmpty(value: unknown): boolean {
|
|
86
|
+
if (value == null) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (Array.isArray(value) || typeof value === 'string') {
|
|
90
|
+
return value.length === 0;
|
|
91
|
+
}
|
|
92
|
+
if (value instanceof Map || value instanceof Set) {
|
|
93
|
+
return value.size === 0;
|
|
94
|
+
}
|
|
95
|
+
if (isPlainObject(value)) {
|
|
96
|
+
return Object.keys(value).length === 0;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Deduplicates array entries while preserving order.
|
|
103
|
+
*
|
|
104
|
+
* @param items - Items to deduplicate.
|
|
105
|
+
* @returns The input items without duplicates.
|
|
106
|
+
*/
|
|
107
|
+
export function uniq<T>(items: T[]): T[] {
|
|
108
|
+
return [...new Set(items)];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Performs deep strict equality comparison.
|
|
113
|
+
*
|
|
114
|
+
* @param a - First value.
|
|
115
|
+
* @param b - Second value.
|
|
116
|
+
* @returns True when both values are deeply equal.
|
|
117
|
+
*/
|
|
118
|
+
export function deepEqual(a: unknown, b: unknown): boolean {
|
|
119
|
+
return isDeepStrictEqual(a, b);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns a promise that resolves after the specified delay.
|
|
124
|
+
*
|
|
125
|
+
* @param ms - Delay in milliseconds.
|
|
126
|
+
* @returns A promise that resolves when the delay expires.
|
|
127
|
+
*/
|
|
128
|
+
export function delay(ms: number): Promise<void> {
|
|
129
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Wraps a promise with a timeout.
|
|
134
|
+
*
|
|
135
|
+
* @param promise - The promise to resolve.
|
|
136
|
+
* @param timeoutMs - Maximum time to wait in milliseconds.
|
|
137
|
+
* @param message - Optional timeout message.
|
|
138
|
+
* @returns A promise that resolves/rejects with the original promise result, or rejects on timeout.
|
|
139
|
+
*/
|
|
140
|
+
export async function withTimeout<T>(
|
|
141
|
+
promise: Promise<T>,
|
|
142
|
+
timeoutMs: number,
|
|
143
|
+
message?: string,
|
|
144
|
+
): Promise<T> {
|
|
145
|
+
let timeoutId: NodeJS.Timeout | undefined;
|
|
146
|
+
try {
|
|
147
|
+
return await Promise.race([
|
|
148
|
+
promise,
|
|
149
|
+
new Promise<T>((_resolve, reject) => {
|
|
150
|
+
timeoutId = setTimeout(
|
|
151
|
+
() => reject(new TimeoutError(message ?? `Operation timed out after ${timeoutMs}ms`)),
|
|
152
|
+
timeoutMs,
|
|
153
|
+
);
|
|
154
|
+
}),
|
|
155
|
+
]);
|
|
156
|
+
} finally {
|
|
157
|
+
if (timeoutId) {
|
|
158
|
+
clearTimeout(timeoutId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
20
163
|
/**
|
|
21
164
|
* Takes a dictionary from the remote debugger and converts it into a more
|
|
22
165
|
* manageable AppInfo object with understandable keys.
|
|
@@ -26,15 +169,16 @@ export const RESPONSE_LOG_LENGTH = 100;
|
|
|
26
169
|
*/
|
|
27
170
|
export function appInfoFromDict(dict: Record<string, any>): [string, AppInfo] {
|
|
28
171
|
const id = dict.WIRApplicationIdentifierKey;
|
|
29
|
-
const isProxy =
|
|
30
|
-
|
|
31
|
-
|
|
172
|
+
const isProxy =
|
|
173
|
+
typeof dict.WIRIsApplicationProxyKey === 'string'
|
|
174
|
+
? dict.WIRIsApplicationProxyKey.toLowerCase() === 'true'
|
|
175
|
+
: dict.WIRIsApplicationProxyKey;
|
|
32
176
|
// automation enabled can be either from the keys
|
|
33
177
|
// - WIRRemoteAutomationEnabledKey (boolean)
|
|
34
178
|
// - WIRAutomationAvailabilityKey (string or boolean)
|
|
35
179
|
let isAutomationEnabled: boolean | string = !!dict.WIRRemoteAutomationEnabledKey;
|
|
36
|
-
if (
|
|
37
|
-
if (
|
|
180
|
+
if (Object.hasOwn(dict, 'WIRAutomationAvailabilityKey')) {
|
|
181
|
+
if (typeof dict.WIRAutomationAvailabilityKey === 'string') {
|
|
38
182
|
isAutomationEnabled =
|
|
39
183
|
dict.WIRAutomationAvailabilityKey === 'WIRAutomationAvailabilityUnknown'
|
|
40
184
|
? 'Unknown'
|
|
@@ -65,16 +209,16 @@ export function appInfoFromDict(dict: Record<string, any>): [string, AppInfo] {
|
|
|
65
209
|
*/
|
|
66
210
|
export function pageArrayFromDict(pageDict: StringRecord): Page[] {
|
|
67
211
|
return (
|
|
68
|
-
|
|
212
|
+
Object.values(pageDict)
|
|
69
213
|
// count only WIRTypeWeb pages and ignore all others (WIRTypeJavaScript etc)
|
|
70
214
|
.filter(
|
|
71
|
-
(dict) =>
|
|
215
|
+
(dict) => dict.WIRTypeKey === undefined || ACCEPTED_PAGE_TYPES.includes(dict.WIRTypeKey),
|
|
72
216
|
)
|
|
73
217
|
.map((dict) => ({
|
|
74
218
|
id: dict.WIRPageIdentifierKey,
|
|
75
219
|
title: dict.WIRTitleKey,
|
|
76
220
|
url: dict.WIRURLKey,
|
|
77
|
-
isKey:
|
|
221
|
+
isKey: dict.WIRConnectionIdentifierKey !== undefined,
|
|
78
222
|
}))
|
|
79
223
|
);
|
|
80
224
|
}
|
|
@@ -89,7 +233,7 @@ export function pageArrayFromDict(pageDict: StringRecord): Page[] {
|
|
|
89
233
|
* @returns An array of unique application identifier keys matching the bundle ID.
|
|
90
234
|
*/
|
|
91
235
|
export function appIdsForBundle(bundleId: string, appDict: AppDict): string[] {
|
|
92
|
-
const appIds: string[] =
|
|
236
|
+
const appIds: string[] = Object.entries(appDict)
|
|
93
237
|
.filter(([, data]) => data.bundleId === bundleId)
|
|
94
238
|
.map(([key]) => key);
|
|
95
239
|
|
|
@@ -98,7 +242,7 @@ export function appIdsForBundle(bundleId: string, appDict: AppDict): string[] {
|
|
|
98
242
|
return appIdsForBundle(WEB_CONTENT_BUNDLE_ID, appDict);
|
|
99
243
|
}
|
|
100
244
|
|
|
101
|
-
return
|
|
245
|
+
return uniq(appIds);
|
|
102
246
|
}
|
|
103
247
|
|
|
104
248
|
/**
|
|
@@ -112,8 +256,8 @@ export function appIdsForBundle(bundleId: string, appDict: AppDict): string[] {
|
|
|
112
256
|
*/
|
|
113
257
|
export function checkParams<T extends StringRecord>(params: T): T {
|
|
114
258
|
// check if all parameters have a value
|
|
115
|
-
const errors =
|
|
116
|
-
.filter(([, value]) =>
|
|
259
|
+
const errors = Object.entries(params)
|
|
260
|
+
.filter(([, value]) => value == null)
|
|
117
261
|
.map(([param]) => param);
|
|
118
262
|
if (errors.length) {
|
|
119
263
|
throw new Error(`Missing ${util.pluralize('parameter', errors.length)}: ${errors.join(', ')}`);
|
|
@@ -122,20 +266,41 @@ export function checkParams<T extends StringRecord>(params: T): T {
|
|
|
122
266
|
}
|
|
123
267
|
|
|
124
268
|
/**
|
|
125
|
-
* Converts a value to a JSON string, removing noisy
|
|
126
|
-
*
|
|
269
|
+
* Converts a value to a best-effort JSON string for logging, removing noisy
|
|
270
|
+
* function properties from cloneable objects when possible.
|
|
271
|
+
*
|
|
272
|
+
* Falls back to `String(value)` when JSON serialization returns `undefined`
|
|
273
|
+
* or throws (for example, for functions, symbols, or circular structures).
|
|
127
274
|
*
|
|
128
275
|
* @param value - The value to stringify.
|
|
129
|
-
* @param multiline - If true, formats
|
|
130
|
-
* @returns A
|
|
276
|
+
* @param multiline - If true, formats JSON output with indentation. Defaults to false.
|
|
277
|
+
* @returns A string representation suitable for logging.
|
|
131
278
|
*/
|
|
132
279
|
export function simpleStringify(value: any, multiline: boolean = false): string {
|
|
280
|
+
const stringify = (val: any): string => {
|
|
281
|
+
try {
|
|
282
|
+
return multiline
|
|
283
|
+
? (JSON.stringify(val, null, 2) ?? String(val))
|
|
284
|
+
: (JSON.stringify(val) ?? String(val));
|
|
285
|
+
} catch {
|
|
286
|
+
return String(val);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
133
290
|
if (!value) {
|
|
134
|
-
return
|
|
291
|
+
return stringify(value);
|
|
135
292
|
}
|
|
136
293
|
|
|
137
|
-
|
|
138
|
-
|
|
294
|
+
let cleanValue = value;
|
|
295
|
+
if (typeof value === 'object' && value !== null) {
|
|
296
|
+
try {
|
|
297
|
+
cleanValue = removeNoisyProperties(structuredClone(value));
|
|
298
|
+
} catch {
|
|
299
|
+
// Fall back to the original value when cloning fails (e.g., non-cloneable graph entries).
|
|
300
|
+
cleanValue = value;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return stringify(cleanValue);
|
|
139
304
|
}
|
|
140
305
|
|
|
141
306
|
/**
|
|
@@ -149,29 +314,29 @@ export function simpleStringify(value: any, multiline: boolean = false): string
|
|
|
149
314
|
* an error status code.
|
|
150
315
|
*/
|
|
151
316
|
export function convertJavascriptEvaluationResult(res: any): any {
|
|
152
|
-
if (
|
|
317
|
+
if (res === undefined) {
|
|
153
318
|
throw new Error(
|
|
154
|
-
`Did not get OK result from remote debugger. Result was: ${
|
|
319
|
+
`Did not get OK result from remote debugger. Result was: ${truncateString(simpleStringify(res), RESPONSE_LOG_LENGTH)}`,
|
|
155
320
|
);
|
|
156
|
-
} else if (
|
|
321
|
+
} else if (typeof res === 'string') {
|
|
157
322
|
try {
|
|
158
323
|
res = JSON.parse(res);
|
|
159
324
|
} catch {
|
|
160
325
|
// we might get a serialized object, but we might not
|
|
161
326
|
// if we get here, it is just a value
|
|
162
327
|
}
|
|
163
|
-
} else if (
|
|
328
|
+
} else if ((typeof res !== 'object' && typeof res !== 'function') || res === null) {
|
|
164
329
|
throw new Error(`Result has unexpected type: (${typeof res}).`);
|
|
165
330
|
}
|
|
166
331
|
|
|
167
|
-
if (res
|
|
332
|
+
if (Object.hasOwn(res, 'status') && res.status !== 0) {
|
|
168
333
|
// we got some form of error.
|
|
169
334
|
throw errorFromMJSONWPStatusCode(res.status, res.value.message || res.value);
|
|
170
335
|
}
|
|
171
336
|
|
|
172
337
|
// with either have an object with a `value` property (even if `null`),
|
|
173
338
|
// or a plain object
|
|
174
|
-
const value =
|
|
339
|
+
const value = Object.hasOwn(res, 'value') ? res.value : res;
|
|
175
340
|
return removeNoisyProperties(value);
|
|
176
341
|
}
|
|
177
342
|
|
|
@@ -182,13 +347,24 @@ export function convertJavascriptEvaluationResult(res: any): any {
|
|
|
182
347
|
* @returns The full path to the module root directory.
|
|
183
348
|
* @throws Error if the module root folder cannot be determined.
|
|
184
349
|
*/
|
|
185
|
-
|
|
350
|
+
let cachedModuleRoot: string | undefined;
|
|
351
|
+
/**
|
|
352
|
+
* Calculates and memoizes the path to the current module root.
|
|
353
|
+
*
|
|
354
|
+
* @returns The full path to the module root directory.
|
|
355
|
+
* @throws Error if the module root folder cannot be determined.
|
|
356
|
+
*/
|
|
357
|
+
export function getModuleRoot(): string {
|
|
358
|
+
if (cachedModuleRoot) {
|
|
359
|
+
return cachedModuleRoot;
|
|
360
|
+
}
|
|
186
361
|
const root = node.getModuleRootSync(MODULE_NAME, __filename);
|
|
187
362
|
if (!root) {
|
|
188
363
|
throw new Error(`Cannot find the root folder of the ${MODULE_NAME} Node.js module`);
|
|
189
364
|
}
|
|
365
|
+
cachedModuleRoot = root;
|
|
190
366
|
return root;
|
|
191
|
-
}
|
|
367
|
+
}
|
|
192
368
|
|
|
193
369
|
/**
|
|
194
370
|
* Reads and parses the package.json file from the module root.
|
|
@@ -217,7 +393,7 @@ export function canUseWebInspectorShim(platformVersion: string): boolean {
|
|
|
217
393
|
* @returns The cleaned object.
|
|
218
394
|
*/
|
|
219
395
|
function removeNoisyProperties<T>(obj: T): T {
|
|
220
|
-
if (
|
|
396
|
+
if (obj && typeof obj === 'object') {
|
|
221
397
|
for (const property of ['ceil', 'clone', 'floor', 'round', 'scale', 'toString']) {
|
|
222
398
|
delete obj[property];
|
|
223
399
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"keywords": [
|
|
5
5
|
"appium"
|
|
6
6
|
],
|
|
7
|
-
"version": "15.
|
|
7
|
+
"version": "15.8.1",
|
|
8
8
|
"author": "Appium Contributors",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"repository": {
|
|
@@ -35,11 +35,9 @@
|
|
|
35
35
|
"@appium/base-driver": "^10.0.0-rc.1",
|
|
36
36
|
"@appium/support": "^7.0.0-rc.1",
|
|
37
37
|
"appium-ios-device": "^3.0.0",
|
|
38
|
-
"asyncbox": "^6.1.0",
|
|
39
38
|
"async-lock": "^1.4.1",
|
|
40
|
-
"
|
|
39
|
+
"asyncbox": "^6.1.0",
|
|
41
40
|
"glob": "^13.0.0",
|
|
42
|
-
"lodash": "^4.17.11",
|
|
43
41
|
"teen_process": "^4.0.4"
|
|
44
42
|
},
|
|
45
43
|
"optionalDependencies": {
|
|
@@ -74,8 +72,6 @@
|
|
|
74
72
|
"@appium/types": "^1.0.0-rc.1",
|
|
75
73
|
"@semantic-release/changelog": "^6.0.1",
|
|
76
74
|
"@semantic-release/git": "^10.0.1",
|
|
77
|
-
"@types/bluebird": "^3.5.38",
|
|
78
|
-
"@types/lodash": "^4.14.196",
|
|
79
75
|
"@types/mocha": "^10.0.1",
|
|
80
76
|
"@types/node": "^25.0.0",
|
|
81
77
|
"appium-ios-simulator": "^8.0.0",
|
|
@@ -87,8 +83,8 @@
|
|
|
87
83
|
"mocha-multi-reporters": "^1.5.1",
|
|
88
84
|
"node-simctl": "^8.0.0",
|
|
89
85
|
"prettier": "^3.0.0",
|
|
90
|
-
"serve-static": "^2.2.0",
|
|
91
86
|
"semantic-release": "^25.0.2",
|
|
87
|
+
"serve-static": "^2.2.0",
|
|
92
88
|
"sinon": "^21.0.0",
|
|
93
89
|
"ts-node": "^10.9.1",
|
|
94
90
|
"typescript": "^6.0.3"
|
package/scripts/common.mjs
CHANGED
|
@@ -40,6 +40,48 @@ const ATOMS_DIRECTORY = path.resolve(WORKING_ROOT_DIR, 'atoms');
|
|
|
40
40
|
const LAST_UPDATE_FILE = path.resolve(ATOMS_DIRECTORY, 'lastupdate');
|
|
41
41
|
let bazelCommand;
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Clone the target selenium repository and branch into the temporary directory.
|
|
45
|
+
*/
|
|
46
|
+
export async function seleniumClone () {
|
|
47
|
+
await seleniumMkdir();
|
|
48
|
+
await seleniumClean();
|
|
49
|
+
const cloneArgs = (branch) => ([
|
|
50
|
+
'clone',
|
|
51
|
+
`--branch=${branch}`,
|
|
52
|
+
'--depth=1',
|
|
53
|
+
SELENIUM_GITHUB,
|
|
54
|
+
SELENIUM_DIRECTORY,
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
log.info(`Cloning branch '${SELENIUM_BRANCH}' from '${SELENIUM_GITHUB}'`);
|
|
58
|
+
await exec('git', cloneArgs(SELENIUM_BRANCH));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Builds Selenium atoms and imports them into this repository.
|
|
63
|
+
*
|
|
64
|
+
* @param {boolean} shouldClean - Whether to run `bazel clean` before building.
|
|
65
|
+
*/
|
|
66
|
+
export async function importAtoms(shouldClean) {
|
|
67
|
+
await checkBazel();
|
|
68
|
+
await atomsCleanDir();
|
|
69
|
+
if (shouldClean) {
|
|
70
|
+
await atomsClean();
|
|
71
|
+
}
|
|
72
|
+
await atomsMkdir();
|
|
73
|
+
await atomsBuild();
|
|
74
|
+
const bazelOutDir = await getBazelOutDir();
|
|
75
|
+
const atomsDir = path.resolve(bazelOutDir, BAZEL_WD_ATOMS_DIR);
|
|
76
|
+
const atomsInjectDir = path.resolve(bazelOutDir, BAZEL_WD_ATOMS_INJECT_DIR);
|
|
77
|
+
const fragmentsDir = path.resolve(bazelOutDir, BAZEL_FRAGMENTS_DIR);
|
|
78
|
+
await atomsCopyAtoms(fragmentsDir);
|
|
79
|
+
// copy fragments first and atoms later so atoms overwrite fragments
|
|
80
|
+
await atomsCopyAtoms(atomsDir);
|
|
81
|
+
await atomsCopyAtoms(atomsInjectDir);
|
|
82
|
+
await atomsTimestamp();
|
|
83
|
+
}
|
|
84
|
+
|
|
43
85
|
function getBazelEnv() {
|
|
44
86
|
// Selenium atoms build does not require Android SDK. If these env vars are set locally,
|
|
45
87
|
// Bazel may try to auto-configure Android toolchains and fail on host-specific SDK issues.
|
|
@@ -66,24 +108,6 @@ async function seleniumClean () {
|
|
|
66
108
|
await fs.rimraf(SELENIUM_DIRECTORY);
|
|
67
109
|
}
|
|
68
110
|
|
|
69
|
-
/**
|
|
70
|
-
* Clone the target selenium repository and branch into the temporary directory.
|
|
71
|
-
*/
|
|
72
|
-
export async function seleniumClone () {
|
|
73
|
-
await seleniumMkdir();
|
|
74
|
-
await seleniumClean();
|
|
75
|
-
const cloneArgs = (branch) => ([
|
|
76
|
-
'clone',
|
|
77
|
-
`--branch=${branch}`,
|
|
78
|
-
'--depth=1',
|
|
79
|
-
SELENIUM_GITHUB,
|
|
80
|
-
SELENIUM_DIRECTORY,
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
|
-
log.info(`Cloning branch '${SELENIUM_BRANCH}' from '${SELENIUM_GITHUB}'`);
|
|
84
|
-
await exec('git', cloneArgs(SELENIUM_BRANCH));
|
|
85
|
-
};
|
|
86
|
-
|
|
87
111
|
/**
|
|
88
112
|
* Check bazel version if current available bazel version on the host machine
|
|
89
113
|
* meets Selenium's minimum from `.bazelversion` (newer Bazel is allowed).
|
|
@@ -228,23 +252,4 @@ async function atomsTimestamp () {
|
|
|
228
252
|
log.info(`Recording Selenium revision in atoms dir`);
|
|
229
253
|
const {stdout} = await exec('git', ['log', '-n', '1', '--decorate=full'], {cwd: SELENIUM_DIRECTORY});
|
|
230
254
|
await fs.writeFile(LAST_UPDATE_FILE, Buffer.from(stdout.trimEnd() + '\n'));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export async function importAtoms(shouldClean) {
|
|
234
|
-
await checkBazel();
|
|
235
|
-
await atomsCleanDir();
|
|
236
|
-
if (shouldClean) {
|
|
237
|
-
await atomsClean();
|
|
238
|
-
}
|
|
239
|
-
await atomsMkdir();
|
|
240
|
-
await atomsBuild();
|
|
241
|
-
const bazelOutDir = await getBazelOutDir();
|
|
242
|
-
const atomsDir = path.resolve(bazelOutDir, BAZEL_WD_ATOMS_DIR);
|
|
243
|
-
const atomsInjectDir = path.resolve(bazelOutDir, BAZEL_WD_ATOMS_INJECT_DIR);
|
|
244
|
-
const fragmentsDir = path.resolve(bazelOutDir, BAZEL_FRAGMENTS_DIR);
|
|
245
|
-
await atomsCopyAtoms(fragmentsDir);
|
|
246
|
-
// copy fragments first and atoms later so atoms overwrite fragments
|
|
247
|
-
await atomsCopyAtoms(atomsDir);
|
|
248
|
-
await atomsCopyAtoms(atomsInjectDir);
|
|
249
|
-
await atomsTimestamp();
|
|
250
255
|
};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import { SubProcess } from 'teen_process';
|
|
3
3
|
import { plist, util } from '@appium/support';
|
|
4
|
-
import B from 'bluebird';
|
|
5
4
|
import { getSimulator } from 'appium-ios-simulator';
|
|
6
|
-
import _ from 'lodash';
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
async function getSocket (udid) {
|
|
@@ -83,8 +81,8 @@ async function startSoCat (socket) {
|
|
|
83
81
|
}
|
|
84
82
|
});
|
|
85
83
|
|
|
86
|
-
const prom = new
|
|
87
|
-
proc.on('exit',
|
|
84
|
+
const prom = new Promise((resolve) => {
|
|
85
|
+
proc.on('exit', () => {
|
|
88
86
|
resolve('done');
|
|
89
87
|
});
|
|
90
88
|
});
|
|
@@ -95,7 +93,7 @@ async function startSoCat (socket) {
|
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
async function main () {
|
|
98
|
-
const udid =
|
|
96
|
+
const udid = process.argv.at(-1);
|
|
99
97
|
const s = await getSocket(udid);
|
|
100
98
|
console.log('Simulator web inspector socket:', s);
|
|
101
99
|
await startSoCat(s);
|