appium-remote-debugger 15.2.9 → 15.2.11
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/mixins/connect.d.ts +47 -31
- package/build/lib/mixins/connect.d.ts.map +1 -1
- package/build/lib/mixins/connect.js +105 -86
- package/build/lib/mixins/connect.js.map +1 -1
- package/build/lib/mixins/cookies.d.ts +17 -13
- package/build/lib/mixins/cookies.d.ts.map +1 -1
- package/build/lib/mixins/cookies.js +12 -12
- package/build/lib/mixins/cookies.js.map +1 -1
- package/build/lib/mixins/events.d.ts +32 -31
- package/build/lib/mixins/events.d.ts.map +1 -1
- package/build/lib/mixins/events.js +21 -24
- package/build/lib/mixins/events.js.map +1 -1
- package/build/lib/mixins/execute.d.ts +35 -24
- package/build/lib/mixins/execute.d.ts.map +1 -1
- package/build/lib/mixins/execute.js +34 -26
- package/build/lib/mixins/execute.js.map +1 -1
- package/build/lib/mixins/message-handlers.d.ts +63 -43
- package/build/lib/mixins/message-handlers.d.ts.map +1 -1
- package/build/lib/mixins/message-handlers.js +74 -57
- package/build/lib/mixins/message-handlers.js.map +1 -1
- package/build/lib/mixins/misc.d.ts +34 -24
- package/build/lib/mixins/misc.d.ts.map +1 -1
- package/build/lib/mixins/misc.js +27 -21
- package/build/lib/mixins/misc.js.map +1 -1
- package/build/lib/mixins/navigate.d.ts +39 -27
- package/build/lib/mixins/navigate.d.ts.map +1 -1
- package/build/lib/mixins/navigate.js +31 -31
- package/build/lib/mixins/navigate.js.map +1 -1
- package/build/lib/mixins/screenshot.d.ts +21 -11
- package/build/lib/mixins/screenshot.d.ts.map +1 -1
- package/build/lib/mixins/screenshot.js +10 -14
- package/build/lib/mixins/screenshot.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/lib/mixins/{connect.js → connect.ts} +153 -113
- package/lib/mixins/cookies.ts +61 -0
- package/lib/mixins/events.ts +91 -0
- package/lib/mixins/{execute.js → execute.ts} +59 -33
- package/lib/mixins/message-handlers.ts +272 -0
- package/lib/mixins/misc.ts +136 -0
- package/lib/mixins/{navigate.js → navigate.ts} +44 -41
- package/lib/mixins/screenshot.ts +52 -0
- package/package.json +1 -1
- package/lib/mixins/cookies.js +0 -53
- package/lib/mixins/events.js +0 -81
- package/lib/mixins/message-handlers.js +0 -224
- package/lib/mixins/misc.js +0 -121
- package/lib/mixins/screenshot.js +0 -43
|
@@ -14,37 +14,55 @@ import {
|
|
|
14
14
|
getPageIdKey,
|
|
15
15
|
getGarbageCollectOnExecute,
|
|
16
16
|
} from './property-accessors';
|
|
17
|
+
import type { RemoteDebugger } from '../remote-debugger';
|
|
18
|
+
import type { AppIdKey, PageIdKey } from '../types';
|
|
17
19
|
|
|
18
20
|
/* How many milliseconds to wait for webkit to return a response before timing out */
|
|
19
21
|
const RPC_RESPONSE_TIMEOUT_MS = 5000;
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
*
|
|
24
|
+
* Executes a Selenium atom in Safari by generating the atom script and
|
|
25
|
+
* executing it in the page context.
|
|
23
26
|
*
|
|
24
|
-
* @
|
|
25
|
-
* @param
|
|
26
|
-
* @param
|
|
27
|
-
* @
|
|
28
|
-
* @returns {Promise<any>} The result received from the atom
|
|
27
|
+
* @param atom - Name of the Selenium atom to execute (see atoms/ directory).
|
|
28
|
+
* @param args - Arguments to pass to the atom function. Defaults to empty array.
|
|
29
|
+
* @param frames - Frame context array for frame-specific execution. Defaults to empty array.
|
|
30
|
+
* @returns A promise that resolves to the result received from the atom execution.
|
|
29
31
|
*/
|
|
30
|
-
export async function executeAtom
|
|
32
|
+
export async function executeAtom(
|
|
33
|
+
this: RemoteDebugger,
|
|
34
|
+
atom: string,
|
|
35
|
+
args: any[] = [],
|
|
36
|
+
frames: string[] = []
|
|
37
|
+
): Promise<any> {
|
|
31
38
|
this.log.debug(`Executing atom '${atom}' with 'args=${JSON.stringify(args)}; frames=${frames}'`);
|
|
32
39
|
const script = await getScriptForAtom(atom, args, frames);
|
|
33
40
|
const value = await this.execute(script);
|
|
34
|
-
this.log.debug(`Received result for atom '${atom}' execution: ${_.truncate(simpleStringify(value), {
|
|
41
|
+
this.log.debug(`Received result for atom '${atom}' execution: ${_.truncate(simpleStringify(value), {
|
|
42
|
+
length: RESPONSE_LOG_LENGTH
|
|
43
|
+
})}`);
|
|
35
44
|
return value;
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @
|
|
48
|
+
* Executes a Selenium atom asynchronously by creating a Promise in the page context
|
|
49
|
+
* and waiting for the atom to resolve it. Falls back to polling if Runtime.awaitPromise
|
|
50
|
+
* is not available.
|
|
51
|
+
*
|
|
52
|
+
* @param atom - Name of the Selenium atom to execute (see atoms/ directory).
|
|
53
|
+
* @param args - Arguments to pass to the atom function. Defaults to empty array.
|
|
54
|
+
* If args[2] is provided, it will be used as the timeout in milliseconds.
|
|
55
|
+
* @param frames - Frame context array for frame-specific execution. Defaults to empty array.
|
|
56
|
+
* @returns A promise that resolves to the result received from the atom execution.
|
|
44
57
|
*/
|
|
45
|
-
export async function executeAtomAsync
|
|
58
|
+
export async function executeAtomAsync(
|
|
59
|
+
this: RemoteDebugger,
|
|
60
|
+
atom: string,
|
|
61
|
+
args: any[] = [],
|
|
62
|
+
frames: string[] = []
|
|
63
|
+
): Promise<any> {
|
|
46
64
|
// helper to send directly to the web inspector
|
|
47
|
-
const evaluate = async (method, opts) => await this.requireRpcClient(true).send(method, Object.assign({
|
|
65
|
+
const evaluate = async (method: string, opts: any) => await this.requireRpcClient(true).send(method, Object.assign({
|
|
48
66
|
appIdKey: getAppIdKey(this),
|
|
49
67
|
pageIdKey: getPageIdKey(this),
|
|
50
68
|
returnByValue: false,
|
|
@@ -76,7 +94,7 @@ export async function executeAtomAsync (atom, args = [], frames = []) {
|
|
|
76
94
|
await this.execute(await getScriptForAtom(atom, args, frames, asyncCallBack));
|
|
77
95
|
|
|
78
96
|
// wait for the promise to be resolved
|
|
79
|
-
let res;
|
|
97
|
+
let res: any;
|
|
80
98
|
const subcommandTimeout = 1000; // timeout on individual commands
|
|
81
99
|
try {
|
|
82
100
|
res = await evaluate('Runtime.awaitPromise', {
|
|
@@ -85,7 +103,7 @@ export async function executeAtomAsync (atom, args = [], frames = []) {
|
|
|
85
103
|
generatePreview: true,
|
|
86
104
|
saveResult: true,
|
|
87
105
|
});
|
|
88
|
-
} catch (err) {
|
|
106
|
+
} catch (err: any) {
|
|
89
107
|
if (!err.message.includes(`'Runtime.awaitPromise' was not found`)) {
|
|
90
108
|
throw err;
|
|
91
109
|
}
|
|
@@ -127,13 +145,16 @@ export async function executeAtomAsync (atom, args = [], frames = []) {
|
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* @
|
|
148
|
+
* Executes a JavaScript command in the page context and returns the result.
|
|
149
|
+
* Optionally performs garbage collection before execution if configured.
|
|
150
|
+
*
|
|
151
|
+
* @param command - The JavaScript command string to execute.
|
|
152
|
+
* @param override - Deprecated and unused parameter.
|
|
153
|
+
* @returns A promise that resolves to the result of the JavaScript evaluation,
|
|
154
|
+
* converted to a usable format.
|
|
134
155
|
*/
|
|
135
156
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
136
|
-
export async function execute
|
|
157
|
+
export async function execute(this: RemoteDebugger, command: string, override?: boolean): Promise<any> {
|
|
137
158
|
const {appIdKey, pageIdKey} = checkParams({
|
|
138
159
|
appIdKey: getAppIdKey(this),
|
|
139
160
|
pageIdKey: getPageIdKey(this),
|
|
@@ -145,8 +166,8 @@ export async function execute (command, override) {
|
|
|
145
166
|
|
|
146
167
|
const rpcClient = this.requireRpcClient(true);
|
|
147
168
|
await rpcClient.waitForPage(
|
|
148
|
-
|
|
149
|
-
|
|
169
|
+
appIdKey as AppIdKey,
|
|
170
|
+
pageIdKey as PageIdKey
|
|
150
171
|
);
|
|
151
172
|
this.log.debug(`Sending javascript command: '${_.truncate(command, {length: 50})}'`);
|
|
152
173
|
const res = await rpcClient.send('Runtime.evaluate', {
|
|
@@ -159,12 +180,21 @@ export async function execute (command, override) {
|
|
|
159
180
|
}
|
|
160
181
|
|
|
161
182
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* @param
|
|
183
|
+
* Calls a JavaScript function on a remote object identified by objectId.
|
|
184
|
+
* Optionally performs garbage collection before execution if configured.
|
|
185
|
+
*
|
|
186
|
+
* @param objectId - The object identifier of the remote object to call the function on.
|
|
187
|
+
* @param fn - The function declaration string to execute on the object.
|
|
188
|
+
* @param args - Optional array of arguments to pass to the function.
|
|
189
|
+
* @returns A promise that resolves to the result of the function call,
|
|
190
|
+
* converted to a usable format.
|
|
166
191
|
*/
|
|
167
|
-
export async function callFunction
|
|
192
|
+
export async function callFunction(
|
|
193
|
+
this: RemoteDebugger,
|
|
194
|
+
objectId: string,
|
|
195
|
+
fn: string,
|
|
196
|
+
args?: any[]
|
|
197
|
+
): Promise<any> {
|
|
168
198
|
const {appIdKey, pageIdKey} = checkParams({
|
|
169
199
|
appIdKey: getAppIdKey(this),
|
|
170
200
|
pageIdKey: getPageIdKey(this),
|
|
@@ -186,7 +216,3 @@ export async function callFunction (objectId, fn, args) {
|
|
|
186
216
|
|
|
187
217
|
return convertJavascriptEvaluationResult(res);
|
|
188
218
|
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* @typedef {import('../remote-debugger').RemoteDebugger} RemoteDebugger
|
|
192
|
-
*/
|
|
@@ -0,0 +1,272 @@
|
|
|
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
|
+
import type { RemoteDebugger } from '../remote-debugger';
|
|
18
|
+
import type { StringRecord } from '@appium/types';
|
|
19
|
+
import type { AppDict } from '../types';
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Generic callbacks used throughout the lifecycle of the Remote Debugger.
|
|
23
|
+
* These will be added to the prototype.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Handles page change notifications from the remote debugger.
|
|
28
|
+
* Updates the page array for the specified application and emits a page change
|
|
29
|
+
* event if the pages have actually changed and navigation is not in progress.
|
|
30
|
+
*
|
|
31
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
32
|
+
* @param appIdKey - The application identifier key for which pages have changed.
|
|
33
|
+
* @param pageDict - Dictionary containing the new page information.
|
|
34
|
+
*/
|
|
35
|
+
export async function onPageChange(
|
|
36
|
+
this: RemoteDebugger,
|
|
37
|
+
err: Error | null | undefined,
|
|
38
|
+
appIdKey: string,
|
|
39
|
+
pageDict: StringRecord
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
if (_.isEmpty(pageDict)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const currentPages = pageArrayFromDict(pageDict);
|
|
46
|
+
// save the page dict for this app
|
|
47
|
+
if (getAppDict(this)[appIdKey]) {
|
|
48
|
+
const previousPages = getAppDict(this)[appIdKey].pageArray;
|
|
49
|
+
// we have a pre-existing pageDict
|
|
50
|
+
if (previousPages && _.isEqual(previousPages, currentPages)) {
|
|
51
|
+
this.log.debug(
|
|
52
|
+
`Received page change notice for app '${appIdKey}' ` +
|
|
53
|
+
`but the listing has not changed. Ignoring.`
|
|
54
|
+
);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// keep track of the page dictionary
|
|
58
|
+
getAppDict(this)[appIdKey].pageArray = currentPages;
|
|
59
|
+
this.log.debug(
|
|
60
|
+
`Pages changed for ${appIdKey}: ${JSON.stringify(previousPages)} -> ${JSON.stringify(currentPages)}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (getNavigatingToPage(this)) {
|
|
65
|
+
// in the middle of navigating, so reporting a page change will cause problems
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.emit(events.EVENT_PAGE_CHANGE, {
|
|
70
|
+
appIdKey: appIdKey.replace('PID:', ''),
|
|
71
|
+
pageArray: currentPages,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Handles notifications when a new application connects to the remote debugger.
|
|
77
|
+
* Updates the application dictionary with the new application information.
|
|
78
|
+
*
|
|
79
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
80
|
+
* @param dict - Dictionary containing the new application information including
|
|
81
|
+
* the WIRApplicationIdentifierKey.
|
|
82
|
+
*/
|
|
83
|
+
export async function onAppConnect(
|
|
84
|
+
this: RemoteDebugger,
|
|
85
|
+
err: Error | null | undefined,
|
|
86
|
+
dict: StringRecord
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
const appIdKey = dict.WIRApplicationIdentifierKey;
|
|
89
|
+
this.log.debug(`Notified that new application '${appIdKey}' has connected`);
|
|
90
|
+
updateAppsWithDict.bind(this)(dict);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Handles notifications when an application disconnects from the remote debugger.
|
|
95
|
+
* Removes the application from the dictionary and attempts to find a replacement
|
|
96
|
+
* if the disconnected app was the currently selected one. Emits a disconnect event
|
|
97
|
+
* if no applications remain.
|
|
98
|
+
*
|
|
99
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
100
|
+
* @param dict - Dictionary containing the disconnected application information
|
|
101
|
+
* including the WIRApplicationIdentifierKey.
|
|
102
|
+
*/
|
|
103
|
+
export function onAppDisconnect(
|
|
104
|
+
this: RemoteDebugger,
|
|
105
|
+
err: Error | null | undefined,
|
|
106
|
+
dict: StringRecord
|
|
107
|
+
): void {
|
|
108
|
+
const appIdKey = dict.WIRApplicationIdentifierKey;
|
|
109
|
+
this.log.debug(`Application '${appIdKey}' disconnected. Removing from app dictionary.`);
|
|
110
|
+
this.log.debug(`Current app is '${getAppIdKey(this)}'`);
|
|
111
|
+
|
|
112
|
+
// get rid of the entry in our app dictionary,
|
|
113
|
+
// since it is no longer available
|
|
114
|
+
delete getAppDict(this)[appIdKey];
|
|
115
|
+
|
|
116
|
+
// if the disconnected app is the one we are connected to, try to find another
|
|
117
|
+
if (getAppIdKey(this) === appIdKey) {
|
|
118
|
+
this.log.debug(`No longer have app id. Attempting to find new one.`);
|
|
119
|
+
setAppIdKey(this, getDebuggerAppKey.bind(this)(getBundleId(this) as string));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (_.isEmpty(getAppDict(this))) {
|
|
123
|
+
// this means we no longer have any apps. what the what?
|
|
124
|
+
this.log.debug('Main app disconnected. Disconnecting altogether.');
|
|
125
|
+
this.emit(events.EVENT_DISCONNECT, true);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Handles notifications when an application's information is updated.
|
|
131
|
+
* Updates the application dictionary with the new information while preserving
|
|
132
|
+
* any existing page array data.
|
|
133
|
+
*
|
|
134
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
135
|
+
* @param dict - Dictionary containing the updated application information.
|
|
136
|
+
*/
|
|
137
|
+
export async function onAppUpdate(
|
|
138
|
+
this: RemoteDebugger,
|
|
139
|
+
err: Error | null | undefined,
|
|
140
|
+
dict: StringRecord
|
|
141
|
+
): Promise<void> {
|
|
142
|
+
this.log.debug(`Notified that an application has been updated`);
|
|
143
|
+
updateAppsWithDict.bind(this)(dict);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handles notifications containing the list of connected drivers.
|
|
148
|
+
* Updates the internal connected drivers list with the received information.
|
|
149
|
+
*
|
|
150
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
151
|
+
* @param drivers - Dictionary containing the connected driver list with
|
|
152
|
+
* WIRDriverDictionaryKey.
|
|
153
|
+
*/
|
|
154
|
+
export function onConnectedDriverList(
|
|
155
|
+
this: RemoteDebugger,
|
|
156
|
+
err: Error | null | undefined,
|
|
157
|
+
drivers: StringRecord
|
|
158
|
+
): void {
|
|
159
|
+
setConnectedDrivers(this, drivers.WIRDriverDictionaryKey);
|
|
160
|
+
this.log.debug(`Received connected driver list: ${JSON.stringify(this.connectedDrivers)}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Handles notifications about the current automation availability state.
|
|
165
|
+
* This state changes when 'Remote Automation' setting in Safari's advanced settings
|
|
166
|
+
* is toggled. The state can be either WIRAutomationAvailabilityAvailable or
|
|
167
|
+
* WIRAutomationAvailabilityNotAvailable.
|
|
168
|
+
*
|
|
169
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
170
|
+
* @param state - Dictionary containing the automation availability state with
|
|
171
|
+
* WIRAutomationAvailabilityKey.
|
|
172
|
+
*/
|
|
173
|
+
export function onCurrentState(
|
|
174
|
+
this: RemoteDebugger,
|
|
175
|
+
err: Error | null | undefined,
|
|
176
|
+
state: StringRecord
|
|
177
|
+
): void {
|
|
178
|
+
setCurrentState(this, state.WIRAutomationAvailabilityKey);
|
|
179
|
+
// This state changes when 'Remote Automation' in 'Settings app' > 'Safari' > 'Advanced' > 'Remote Automation' changes
|
|
180
|
+
// WIRAutomationAvailabilityAvailable or WIRAutomationAvailabilityNotAvailable
|
|
181
|
+
this.log.debug(`Received connected automation availability state: ${JSON.stringify(this.currentState)}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Handles notifications containing the list of connected applications.
|
|
186
|
+
* Translates the received information into the application dictionary format,
|
|
187
|
+
* filtering out any applications that are in the skipped apps list.
|
|
188
|
+
*
|
|
189
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
190
|
+
* @param apps - Dictionary containing the connected applications list.
|
|
191
|
+
*/
|
|
192
|
+
export async function onConnectedApplicationList(
|
|
193
|
+
this: RemoteDebugger,
|
|
194
|
+
err: Error | null | undefined,
|
|
195
|
+
apps: StringRecord
|
|
196
|
+
): Promise<void> {
|
|
197
|
+
this.log.debug(`Received connected applications list: ${_.keys(apps).join(', ')}`);
|
|
198
|
+
|
|
199
|
+
// translate the received information into an easier-to-manage
|
|
200
|
+
// hash with app id as key, and app info as value
|
|
201
|
+
const newDict: AppDict = {};
|
|
202
|
+
for (const dict of _.values(apps)) {
|
|
203
|
+
const [id, entry] = appInfoFromDict(dict);
|
|
204
|
+
if (getSkippedApps(this).includes(entry.name)) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
newDict[id] = entry;
|
|
208
|
+
}
|
|
209
|
+
// update the object's list of apps
|
|
210
|
+
_.defaults(getAppDict(this), newDict);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Given a bundle ID, finds the correct remote debugger app identifier key
|
|
215
|
+
* that is currently connected. Also handles proxy applications that may act
|
|
216
|
+
* on behalf of the requested bundle ID.
|
|
217
|
+
*
|
|
218
|
+
* @param bundleId - The bundle identifier to search for.
|
|
219
|
+
* @returns The application identifier key if found, undefined otherwise.
|
|
220
|
+
* If a proxy application is found, returns the proxy's app ID instead.
|
|
221
|
+
*/
|
|
222
|
+
export function getDebuggerAppKey(this: RemoteDebugger, bundleId: string): string | undefined {
|
|
223
|
+
let appId: string | undefined;
|
|
224
|
+
for (const [key, data] of _.toPairs(getAppDict(this))) {
|
|
225
|
+
if (data.bundleId === bundleId) {
|
|
226
|
+
appId = key;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// now we need to determine if we should pick a proxy for this instead
|
|
231
|
+
if (appId) {
|
|
232
|
+
this.log.debug(`Found app id key '${appId}' for bundle '${bundleId}'`);
|
|
233
|
+
let proxyAppId: string | undefined;
|
|
234
|
+
for (const [key, data] of _.toPairs(getAppDict(this))) {
|
|
235
|
+
if (data.isProxy && data.hostId === appId) {
|
|
236
|
+
this.log.debug(`Found separate bundleId '${data.bundleId}' ` +
|
|
237
|
+
`acting as proxy for '${bundleId}', with app id '${key}'`);
|
|
238
|
+
// set the app id... the last one will be used, so just keep re-assigning
|
|
239
|
+
proxyAppId = key;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (proxyAppId) {
|
|
243
|
+
appId = proxyAppId;
|
|
244
|
+
this.log.debug(`Using proxied app id '${appId}'`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return appId;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Updates the application dictionary with information from the provided dictionary.
|
|
253
|
+
* Preserves existing page array data if the application already exists in the dictionary.
|
|
254
|
+
* Attempts to set the app ID key if one is not currently set.
|
|
255
|
+
*
|
|
256
|
+
* @param dict - Dictionary containing application information to add or update.
|
|
257
|
+
*/
|
|
258
|
+
function updateAppsWithDict(this: RemoteDebugger, dict: StringRecord): void {
|
|
259
|
+
// get the dictionary entry into a nice form, and add it to the
|
|
260
|
+
// application dictionary
|
|
261
|
+
const [id, entry] = appInfoFromDict(dict);
|
|
262
|
+
if (getAppDict(this)[id]?.pageArray) {
|
|
263
|
+
// preserve the page dictionary for this entry
|
|
264
|
+
entry.pageArray = getAppDict(this)[id].pageArray;
|
|
265
|
+
}
|
|
266
|
+
getAppDict(this)[id] = entry;
|
|
267
|
+
|
|
268
|
+
// try to get the app id from our connected apps
|
|
269
|
+
if (!getAppIdKey(this)) {
|
|
270
|
+
setAppIdKey(this, getDebuggerAppKey.bind(this)(getBundleId(this) as string));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { checkParams } from '../utils';
|
|
2
|
+
import B, { TimeoutError as BTimeoutError } from 'bluebird';
|
|
3
|
+
import {
|
|
4
|
+
getAppIdKey,
|
|
5
|
+
getPageIdKey,
|
|
6
|
+
} from './property-accessors';
|
|
7
|
+
import type { RemoteDebugger } from '../remote-debugger';
|
|
8
|
+
|
|
9
|
+
const SAFARI_BUNDLE_ID = 'com.apple.mobilesafari';
|
|
10
|
+
const GARBAGE_COLLECT_TIMEOUT_MS = 5000;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Launches Safari application on the device by sending a launch command
|
|
14
|
+
* to the remote debugger.
|
|
15
|
+
*/
|
|
16
|
+
export async function launchSafari(this: RemoteDebugger): Promise<void> {
|
|
17
|
+
await this.requireRpcClient().send('launchApplication', {
|
|
18
|
+
bundleId: SAFARI_BUNDLE_ID
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Starts recording the timeline by registering an event listener and
|
|
24
|
+
* sending the Timeline.start command to the remote debugger.
|
|
25
|
+
*
|
|
26
|
+
* @param fn - Event listener function that will be called when timeline events are recorded.
|
|
27
|
+
* @returns A promise that resolves when the timeline recording has started.
|
|
28
|
+
*/
|
|
29
|
+
export async function startTimeline(
|
|
30
|
+
this: RemoteDebugger,
|
|
31
|
+
fn: import('../types').EventListener
|
|
32
|
+
): Promise<any> {
|
|
33
|
+
this.log.debug('Starting to record the timeline');
|
|
34
|
+
this.requireRpcClient().on('Timeline.eventRecorded', fn);
|
|
35
|
+
return await this.requireRpcClient().send('Timeline.start', {
|
|
36
|
+
appIdKey: getAppIdKey(this),
|
|
37
|
+
pageIdKey: getPageIdKey(this),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Stops recording the timeline by sending the Timeline.stop command
|
|
43
|
+
* to the remote debugger.
|
|
44
|
+
*/
|
|
45
|
+
export async function stopTimeline(this: RemoteDebugger): Promise<any> {
|
|
46
|
+
this.log.debug('Stopping to record the timeline');
|
|
47
|
+
await this.requireRpcClient().send('Timeline.stop', {
|
|
48
|
+
appIdKey: getAppIdKey(this),
|
|
49
|
+
pageIdKey: getPageIdKey(this),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Overrides the user agent string for the current page.
|
|
55
|
+
* Note: This may not work for mobile Safari.
|
|
56
|
+
*
|
|
57
|
+
* @param value - The user agent string to set.
|
|
58
|
+
* @returns A promise that resolves when the user agent has been overridden.
|
|
59
|
+
*/
|
|
60
|
+
export async function overrideUserAgent(this: RemoteDebugger, value: string): Promise<any> {
|
|
61
|
+
this.log.debug('Setting overrideUserAgent');
|
|
62
|
+
return await this.requireRpcClient().send('Page.overrideUserAgent', {
|
|
63
|
+
appIdKey: getAppIdKey(this),
|
|
64
|
+
pageIdKey: getPageIdKey(this),
|
|
65
|
+
value,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Checks whether JavaScript execution is currently blocked on the page
|
|
71
|
+
* by attempting to execute a simple JavaScript command with a timeout.
|
|
72
|
+
*
|
|
73
|
+
* @param timeoutMs - The maximum amount of milliseconds to wait for a JavaScript
|
|
74
|
+
* command response. Defaults to 1000ms.
|
|
75
|
+
* @returns A promise that resolves to true if JavaScript execution is blocked,
|
|
76
|
+
* false if it is not blocked.
|
|
77
|
+
*/
|
|
78
|
+
export async function isJavascriptExecutionBlocked(
|
|
79
|
+
this: RemoteDebugger,
|
|
80
|
+
timeoutMs: number = 1000
|
|
81
|
+
): Promise<boolean> {
|
|
82
|
+
try {
|
|
83
|
+
await B.resolve(
|
|
84
|
+
this.requireRpcClient().send('Runtime.evaluate', {
|
|
85
|
+
expression: '1+1;',
|
|
86
|
+
returnByValue: true,
|
|
87
|
+
appIdKey: getAppIdKey(this),
|
|
88
|
+
pageIdKey: getPageIdKey(this),
|
|
89
|
+
})
|
|
90
|
+
).timeout(timeoutMs);
|
|
91
|
+
return false;
|
|
92
|
+
} catch {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Triggers garbage collection on the page's JavaScript heap.
|
|
99
|
+
* This method will gracefully handle cases where garbage collection cannot
|
|
100
|
+
* be performed (e.g., when not connected to a page).
|
|
101
|
+
*
|
|
102
|
+
* @param timeoutMs - Maximum time in milliseconds to wait for garbage collection
|
|
103
|
+
* to complete. Defaults to GARBAGE_COLLECT_TIMEOUT_MS (5000ms).
|
|
104
|
+
*/
|
|
105
|
+
export async function garbageCollect(
|
|
106
|
+
this: RemoteDebugger,
|
|
107
|
+
timeoutMs: number = GARBAGE_COLLECT_TIMEOUT_MS
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
this.log.debug(`Garbage collecting with ${timeoutMs}ms timeout`);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
checkParams({
|
|
113
|
+
appIdKey: getAppIdKey(this),
|
|
114
|
+
pageIdKey: getPageIdKey(this),
|
|
115
|
+
});
|
|
116
|
+
} catch {
|
|
117
|
+
this.log.debug(`Unable to collect garbage at this time`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await B.resolve(this.requireRpcClient().send(
|
|
123
|
+
'Heap.gc', {
|
|
124
|
+
appIdKey: getAppIdKey(this),
|
|
125
|
+
pageIdKey: getPageIdKey(this),
|
|
126
|
+
})
|
|
127
|
+
).timeout(timeoutMs);
|
|
128
|
+
this.log.debug(`Garbage collection successful`);
|
|
129
|
+
} catch (e: any) {
|
|
130
|
+
if (e instanceof BTimeoutError) {
|
|
131
|
+
this.log.debug(`Garbage collection timed out after ${timeoutMs}ms`);
|
|
132
|
+
} else {
|
|
133
|
+
this.log.debug(`Unable to collect garbage: ${e.message}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|