appium-remote-debugger 5.3.0 → 5.7.0
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/build/lib/helpers.js +28 -2
- package/build/lib/remote-debugger-message-handler.js +21 -23
- package/build/lib/remote-debugger-real-device.js +2 -1
- package/build/lib/remote-debugger.js +54 -32
- package/build/lib/remote-messages.js +16 -2
- package/build/lib/rpc-client-real-device.js +5 -1
- package/build/lib/rpc-client-simulator.js +5 -1
- package/build/lib/rpc-client.js +36 -10
- package/lib/helpers.js +29 -1
- package/lib/remote-debugger-message-handler.js +22 -24
- package/lib/remote-debugger-real-device.js +1 -0
- package/lib/remote-debugger.js +75 -39
- package/lib/remote-messages.js +14 -1
- package/lib/rpc-client-real-device.js +3 -0
- package/lib/rpc-client-simulator.js +4 -0
- package/lib/rpc-client.js +30 -7
- package/package.json +1 -1
package/lib/remote-debugger.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import events from 'events';
|
|
2
2
|
import log from './logger';
|
|
3
|
-
import {
|
|
3
|
+
import { errors } from 'appium-base-driver';
|
|
4
4
|
import RpcClientSimulator from './rpc-client-simulator';
|
|
5
5
|
import messageHandlers from './message-handlers';
|
|
6
6
|
import { appInfoFromDict, pageArrayFromDict, getDebuggerAppKey,
|
|
7
7
|
getPossibleDebuggerAppKeys, checkParams, getScriptForAtom,
|
|
8
|
-
simpleStringify, deferredPromise, getElapsedTime
|
|
8
|
+
simpleStringify, deferredPromise, getElapsedTime, convertResult,
|
|
9
|
+
RESPONSE_LOG_LENGTH } from './helpers';
|
|
9
10
|
import { util } from 'appium-support';
|
|
10
11
|
import { retryInterval } from 'asyncbox';
|
|
11
12
|
import _ from 'lodash';
|
|
12
13
|
import B from 'bluebird';
|
|
13
14
|
import path from 'path';
|
|
15
|
+
import UUID from 'uuid-js';
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
let VERSION;
|
|
@@ -29,8 +31,6 @@ const RPC_RESPONSE_TIMEOUT_MS = 5000;
|
|
|
29
31
|
|
|
30
32
|
const PAGE_READY_TIMEOUT = 5000;
|
|
31
33
|
|
|
32
|
-
const RESPONSE_LOG_LENGTH = 100;
|
|
33
|
-
|
|
34
34
|
const GARBAGE_COLLECT_TIMEOUT = 5000;
|
|
35
35
|
|
|
36
36
|
class RemoteDebugger extends events.EventEmitter {
|
|
@@ -134,6 +134,7 @@ class RemoteDebugger extends events.EventEmitter {
|
|
|
134
134
|
|
|
135
135
|
// initialize the rpc client
|
|
136
136
|
this.rpcClient = new RpcClientSimulator({
|
|
137
|
+
bundleId: this.bundleId,
|
|
137
138
|
platformVersion: this.platformVersion,
|
|
138
139
|
isSafari: this.isSafari,
|
|
139
140
|
host: this.host,
|
|
@@ -302,6 +303,7 @@ class RemoteDebugger extends events.EventEmitter {
|
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
}
|
|
306
|
+
|
|
305
307
|
log.debug(`Selected app after ${getElapsedTime(startTime)}ms`);
|
|
306
308
|
return fullPageArray;
|
|
307
309
|
} finally {
|
|
@@ -310,7 +312,7 @@ class RemoteDebugger extends events.EventEmitter {
|
|
|
310
312
|
}
|
|
311
313
|
|
|
312
314
|
async searchForApp (currentUrl, maxTries, ignoreAboutBlankUrl) {
|
|
313
|
-
const bundleIds = this.includeSafari
|
|
315
|
+
const bundleIds = this.includeSafari && !this.isSafari
|
|
314
316
|
? [this.bundleId, SAFARI_BUNDLE_ID]
|
|
315
317
|
: [this.bundleId];
|
|
316
318
|
try {
|
|
@@ -401,13 +403,71 @@ class RemoteDebugger extends events.EventEmitter {
|
|
|
401
403
|
return value;
|
|
402
404
|
}
|
|
403
405
|
|
|
404
|
-
async executeAtomAsync (atom, args, frames
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
406
|
+
async executeAtomAsync (atom, args, frames) {
|
|
407
|
+
// first create a Promise on the page, saving the resolve/reject functions
|
|
408
|
+
// as properties
|
|
409
|
+
const promiseName = `appiumAsyncExecutePromise${UUID.create().toString().replace(/-/g, '')}`;
|
|
410
|
+
let script = `var res, rej;` +
|
|
411
|
+
`window.${promiseName} = new Promise(function (resolve, reject) {` +
|
|
412
|
+
` res = resolve;` +
|
|
413
|
+
` rej = reject;` +
|
|
414
|
+
`});` +
|
|
415
|
+
`window.${promiseName}.resolve = res;` +
|
|
416
|
+
`window.${promiseName}.reject = rej;` +
|
|
417
|
+
`window.${promiseName};`;
|
|
418
|
+
const obj = await this.rpcClient.send('sendJSCommand', {
|
|
419
|
+
command: script,
|
|
420
|
+
appIdKey: this.appIdKey,
|
|
421
|
+
pageIdKey: this.pageIdKey,
|
|
422
|
+
returnByValue: false,
|
|
423
|
+
});
|
|
424
|
+
const promiseObjectId = obj.result.objectId;
|
|
425
|
+
|
|
426
|
+
// execute the atom, calling back to the resolve function
|
|
427
|
+
const asyncCallBack = `function (res) {` +
|
|
428
|
+
` window.${promiseName}.resolve(res);` +
|
|
429
|
+
` window.${promiseName}Value = res;` +
|
|
430
|
+
`}`;
|
|
431
|
+
await this.execute(await getScriptForAtom(atom, args, frames, asyncCallBack));
|
|
432
|
+
|
|
433
|
+
// wait for the promise to be resolved
|
|
434
|
+
let res;
|
|
435
|
+
const subcommandTimeout = 1000; // timeout on individual commands
|
|
436
|
+
try {
|
|
437
|
+
res = await this.rpcClient.send('awaitPromise', {
|
|
438
|
+
promiseObjectId,
|
|
439
|
+
appIdKey: this.appIdKey,
|
|
440
|
+
pageIdKey: this.pageIdKey,
|
|
441
|
+
});
|
|
442
|
+
} catch (err) {
|
|
443
|
+
if (!err.message.includes(`'Runtime.awaitPromise' was not found`)) {
|
|
444
|
+
throw err;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// awaitPromise is not always available, so simulate it with poll
|
|
448
|
+
const retryWait = 100;
|
|
449
|
+
const timeout = (args.length >= 3) ? args[2] : RPC_RESPONSE_TIMEOUT_MS;
|
|
450
|
+
const retries = parseInt(timeout / retryWait, 10);
|
|
451
|
+
const startTime = process.hrtime();
|
|
452
|
+
res = await retryInterval(retries, retryWait, async () => {
|
|
453
|
+
// the atom _will_ return, either because it finished or an error
|
|
454
|
+
// including a timeout error
|
|
455
|
+
if (await this.executeAtom('execute_script', [`return window.hasOwnProperty('${promiseName}Value');`, [null, null], subcommandTimeout], frames)) {
|
|
456
|
+
// we only put the property on `window` when the callback is called,
|
|
457
|
+
// so if it is there, everything is done
|
|
458
|
+
return await this.executeAtom('execute_script', [`return window.${promiseName}Value;`, [null, null], subcommandTimeout], frames);
|
|
459
|
+
}
|
|
460
|
+
// throw a TimeoutError, or else it needs to be caught and re-thrown
|
|
461
|
+
throw new errors.TimeoutError(`Timed out waiting for asynchronous script ` +
|
|
462
|
+
`result after ${getElapsedTime(startTime)} ms'));`);
|
|
463
|
+
});
|
|
464
|
+
} finally {
|
|
465
|
+
try {
|
|
466
|
+
// try to get rid of the promise
|
|
467
|
+
await this.executeAtom('execute_script', [`delete window.${promiseName};`, [null, null], subcommandTimeout], frames);
|
|
468
|
+
} catch (ign) {}
|
|
469
|
+
}
|
|
470
|
+
return convertResult(res);
|
|
411
471
|
}
|
|
412
472
|
|
|
413
473
|
frameDetached () {
|
|
@@ -631,14 +691,14 @@ class RemoteDebugger extends events.EventEmitter {
|
|
|
631
691
|
await this.garbageCollect();
|
|
632
692
|
}
|
|
633
693
|
|
|
634
|
-
log.debug(`Sending javascript command ${_.truncate(command, {length: 50})}`);
|
|
694
|
+
log.debug(`Sending javascript command: '${_.truncate(command, {length: 50})}'`);
|
|
635
695
|
let res = await this.rpcClient.send('sendJSCommand', {
|
|
636
696
|
command,
|
|
637
697
|
appIdKey: this.appIdKey,
|
|
638
698
|
pageIdKey: this.pageIdKey,
|
|
639
699
|
});
|
|
640
700
|
|
|
641
|
-
return
|
|
701
|
+
return convertResult(res);
|
|
642
702
|
}
|
|
643
703
|
|
|
644
704
|
async callFunction (objId, fn, args) {
|
|
@@ -658,31 +718,7 @@ class RemoteDebugger extends events.EventEmitter {
|
|
|
658
718
|
pageIdKey: this.pageIdKey,
|
|
659
719
|
});
|
|
660
720
|
|
|
661
|
-
return
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
convertResult (res) {
|
|
665
|
-
if (_.isUndefined(res)) {
|
|
666
|
-
throw new Error(`Did not get OK result from remote debugger. Result was: ${_.truncate(simpleStringify(res), {length: RESPONSE_LOG_LENGTH})}`);
|
|
667
|
-
} else if (_.isString(res)) {
|
|
668
|
-
try {
|
|
669
|
-
res = JSON.parse(res);
|
|
670
|
-
} catch (err) {
|
|
671
|
-
// we might get a serialized object, but we might not
|
|
672
|
-
// if we get here, it is just a value
|
|
673
|
-
}
|
|
674
|
-
} else if (!_.isObject(res)) {
|
|
675
|
-
throw new Error(`Result has unexpected type: (${typeof res}).`);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
if (res.status && res.status !== 0) {
|
|
679
|
-
// we got some form of error.
|
|
680
|
-
throw errorFromCode(res.status, res.value.message || res.value);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// with either have an object with a `value` property (even if `null`),
|
|
684
|
-
// or a plain object
|
|
685
|
-
return res.hasOwnProperty('value') ? res.value : res;
|
|
721
|
+
return convertResult(res);
|
|
686
722
|
}
|
|
687
723
|
|
|
688
724
|
allowNavigationWithoutReload (allow = true) {
|
package/lib/remote-messages.js
CHANGED
|
@@ -83,7 +83,7 @@ class RemoteMessages {
|
|
|
83
83
|
const method = 'Runtime.evaluate';
|
|
84
84
|
const params = {
|
|
85
85
|
expression: opts.command,
|
|
86
|
-
returnByValue: true,
|
|
86
|
+
returnByValue: _.isBoolean(opts.returnByValue) ? opts.returnByValue : true,
|
|
87
87
|
};
|
|
88
88
|
return this.getFullCommand(connId, senderId, appIdKey, pageIdKey, method, params);
|
|
89
89
|
}
|
|
@@ -173,6 +173,17 @@ class RemoteMessages {
|
|
|
173
173
|
return this.getFullCommand(connId, senderId, appIdKey, pageIdKey, method, params);
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
awaitPromise (connId, senderId, appIdKey, pageIdKey, opts) {
|
|
177
|
+
const method = 'Runtime.awaitPromise';
|
|
178
|
+
const params = {
|
|
179
|
+
promiseObjectId: opts.promiseObjectId,
|
|
180
|
+
returnByValue: true,
|
|
181
|
+
generatePreview: true,
|
|
182
|
+
saveResult: true,
|
|
183
|
+
};
|
|
184
|
+
return this.getFullCommand(connId, senderId, appIdKey, pageIdKey, method, params);
|
|
185
|
+
}
|
|
186
|
+
|
|
176
187
|
|
|
177
188
|
/*
|
|
178
189
|
* Internal functions
|
|
@@ -249,6 +260,8 @@ class RemoteMessages {
|
|
|
249
260
|
return this.deleteCookie(connId, senderId, appIdKey, pageIdKey, opts);
|
|
250
261
|
case 'garbageCollect':
|
|
251
262
|
return this.garbageCollect(connId, senderId, appIdKey, pageIdKey);
|
|
263
|
+
case 'awaitPromise':
|
|
264
|
+
return this.awaitPromise(connId, senderId, appIdKey, pageIdKey, opts);
|
|
252
265
|
default:
|
|
253
266
|
throw new Error(`Unknown command: ${command}`);
|
|
254
267
|
}
|
package/lib/rpc-client.js
CHANGED
|
@@ -6,6 +6,7 @@ import B from 'bluebird';
|
|
|
6
6
|
import UUID from 'uuid-js';
|
|
7
7
|
import RpcMessageHandler from './remote-debugger-message-handler';
|
|
8
8
|
import { isTargetBased, getElapsedTime } from './helpers';
|
|
9
|
+
import { util } from 'appium-support';
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
const DATA_LOG_LENGTH = {length: 200};
|
|
@@ -13,24 +14,30 @@ const DATA_LOG_LENGTH = {length: 200};
|
|
|
13
14
|
const WAIT_FOR_TARGET_RETRIES = 10;
|
|
14
15
|
const WAIT_FOR_TARGET_INTERVAL = 1000;
|
|
15
16
|
|
|
17
|
+
const GENERIC_TARGET_ID = 'page-6';
|
|
18
|
+
|
|
16
19
|
export default class RpcClient {
|
|
17
20
|
constructor (opts = {}) {
|
|
18
21
|
this._targets = [];
|
|
19
22
|
this._shouldCheckForTarget = !!opts.shouldCheckForTarget;
|
|
20
23
|
|
|
21
24
|
const {
|
|
25
|
+
bundleId,
|
|
22
26
|
platformVersion = {},
|
|
23
27
|
isSafari = true,
|
|
24
28
|
specialMessageHandlers = {},
|
|
25
29
|
logFullResponse = false,
|
|
26
30
|
} = opts;
|
|
27
31
|
|
|
32
|
+
this.isSafari = isSafari;
|
|
33
|
+
|
|
28
34
|
this.connected = false;
|
|
29
35
|
this.connId = UUID.create().toString();
|
|
30
36
|
this.senderId = UUID.create().toString();
|
|
31
37
|
this.msgId = 0;
|
|
32
38
|
this.logFullResponse = logFullResponse;
|
|
33
39
|
|
|
40
|
+
this.bundleId = bundleId;
|
|
34
41
|
this.platformVersion = platformVersion;
|
|
35
42
|
|
|
36
43
|
// message handlers
|
|
@@ -68,17 +75,33 @@ export default class RpcClient {
|
|
|
68
75
|
|
|
69
76
|
// on iOS less than 13 have targets that can be computed easily
|
|
70
77
|
// and sometimes is not reported by the Web Inspector
|
|
71
|
-
if (
|
|
72
|
-
this.
|
|
78
|
+
if (util.compareVersions(this.platformVersion, '<', '13.0') && _.isEmpty(this.getTarget(appIdKey, pageIdKey))) {
|
|
79
|
+
if (this.isSafari) {
|
|
80
|
+
this.addTarget({targetId: `page-${pageIdKey}`});
|
|
81
|
+
} else {
|
|
82
|
+
const targets = this.targets[appIdKey];
|
|
83
|
+
const targetIds = _.values(targets)
|
|
84
|
+
.map((targetId) => targetId.replace('page-', ''))
|
|
85
|
+
.sort();
|
|
86
|
+
const lastTargetId = _.last(targetIds) || 0;
|
|
87
|
+
this.addTarget({targetId: `page-${lastTargetId + 1}`});
|
|
88
|
+
}
|
|
73
89
|
return;
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
// otherwise waiting is necessary to see what the target is
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
try {
|
|
94
|
+
await retryInterval(WAIT_FOR_TARGET_RETRIES, WAIT_FOR_TARGET_INTERVAL, () => {
|
|
95
|
+
if (_.isEmpty(this.getTarget(appIdKey, pageIdKey))) {
|
|
96
|
+
throw new Error('No targets found, unable to communicate with device');
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// on some systems sometimes the Web Inspector never sends the target event
|
|
101
|
+
// though the target is available
|
|
102
|
+
log.debug(`No target found. Trying '${GENERIC_TARGET_ID}', which seems to work`);
|
|
103
|
+
this.addTarget({targetId: GENERIC_TARGET_ID});
|
|
104
|
+
}
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
async send (command, opts = {}) {
|