appium-remote-debugger 11.4.2 → 11.5.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/mixins/connect.d.ts +30 -56
  3. package/build/lib/mixins/connect.d.ts.map +1 -1
  4. package/build/lib/mixins/connect.js +57 -90
  5. package/build/lib/mixins/connect.js.map +1 -1
  6. package/build/lib/mixins/cookies.d.ts +22 -0
  7. package/build/lib/mixins/cookies.d.ts.map +1 -0
  8. package/build/lib/mixins/cookies.js +48 -0
  9. package/build/lib/mixins/cookies.js.map +1 -0
  10. package/build/lib/mixins/events.d.ts +17 -2
  11. package/build/lib/mixins/events.d.ts.map +1 -1
  12. package/build/lib/mixins/events.js +29 -2
  13. package/build/lib/mixins/events.js.map +1 -1
  14. package/build/lib/mixins/execute.d.ts +5 -12
  15. package/build/lib/mixins/execute.d.ts.map +1 -1
  16. package/build/lib/mixins/execute.js +18 -30
  17. package/build/lib/mixins/execute.js.map +1 -1
  18. package/build/lib/mixins/message-handlers.d.ts +13 -23
  19. package/build/lib/mixins/message-handlers.d.ts.map +1 -1
  20. package/build/lib/mixins/message-handlers.js +56 -27
  21. package/build/lib/mixins/message-handlers.js.map +1 -1
  22. package/build/lib/mixins/misc.d.ts +51 -0
  23. package/build/lib/mixins/misc.d.ts.map +1 -0
  24. package/build/lib/mixins/misc.js +132 -0
  25. package/build/lib/mixins/misc.js.map +1 -0
  26. package/build/lib/mixins/navigate.d.ts +20 -29
  27. package/build/lib/mixins/navigate.d.ts.map +1 -1
  28. package/build/lib/mixins/navigate.js +25 -23
  29. package/build/lib/mixins/navigate.js.map +1 -1
  30. package/build/lib/mixins/screenshot.d.ts +13 -0
  31. package/build/lib/mixins/screenshot.d.ts.map +1 -0
  32. package/build/lib/mixins/screenshot.js +31 -0
  33. package/build/lib/mixins/screenshot.js.map +1 -0
  34. package/build/lib/remote-debugger-real-device.d.ts +16 -2
  35. package/build/lib/remote-debugger-real-device.d.ts.map +1 -1
  36. package/build/lib/remote-debugger-real-device.js +11 -1
  37. package/build/lib/remote-debugger-real-device.js.map +1 -1
  38. package/build/lib/remote-debugger.d.ts +153 -174
  39. package/build/lib/remote-debugger.d.ts.map +1 -1
  40. package/build/lib/remote-debugger.js +147 -213
  41. package/build/lib/remote-debugger.js.map +1 -1
  42. package/build/lib/rpc/rpc-client.d.ts +2 -1
  43. package/build/lib/rpc/rpc-client.d.ts.map +1 -1
  44. package/build/lib/rpc/rpc-client.js +2 -0
  45. package/build/lib/rpc/rpc-client.js.map +1 -1
  46. package/build/lib/utils.d.ts +20 -16
  47. package/build/lib/utils.d.ts.map +1 -1
  48. package/build/lib/utils.js +33 -25
  49. package/build/lib/utils.js.map +1 -1
  50. package/build/tsconfig.tsbuildinfo +1 -1
  51. package/lib/mixins/connect.js +61 -105
  52. package/lib/mixins/cookies.js +45 -0
  53. package/lib/mixins/events.js +25 -1
  54. package/lib/mixins/execute.js +18 -39
  55. package/lib/mixins/message-handlers.js +67 -36
  56. package/lib/mixins/misc.js +128 -0
  57. package/lib/mixins/navigate.js +26 -30
  58. package/lib/mixins/screenshot.js +34 -0
  59. package/lib/remote-debugger-real-device.js +14 -1
  60. package/lib/remote-debugger.js +133 -305
  61. package/lib/rpc/rpc-client.js +3 -1
  62. package/lib/utils.js +42 -38
  63. package/package.json +1 -1
  64. package/build/lib/mixins/index.d.ts +0 -5
  65. package/build/lib/mixins/index.d.ts.map +0 -1
  66. package/build/lib/mixins/index.js +0 -16
  67. package/build/lib/mixins/index.js.map +0 -1
  68. package/lib/mixins/index.js +0 -14
@@ -1,6 +1,11 @@
1
- import log from '../logger';
2
1
  import events from './events';
3
- import { pageArrayFromDict, getDebuggerAppKey, simpleStringify, appInfoFromDict } from '../utils';
2
+ import {
3
+ pageArrayFromDict,
4
+ getDebuggerAppKey,
5
+ simpleStringify,
6
+ appInfoFromDict,
7
+ deferredPromise,
8
+ } from '../utils';
4
9
  import _ from 'lodash';
5
10
 
6
11
 
@@ -16,14 +21,14 @@ import _ from 'lodash';
16
21
  * @param {Record<string, any>} pageDict
17
22
  * @returns {Promise<void>}
18
23
  */
19
- async function onPageChange (err, appIdKey, pageDict) {
24
+ export async function onPageChange (err, appIdKey, pageDict) {
20
25
  if (_.isEmpty(pageDict)) {
21
26
  return;
22
27
  }
23
28
 
24
29
  const pageArray = pageArrayFromDict(pageDict);
25
30
 
26
- await this.useAppDictLock((done) => {
31
+ await useAppDictLock.bind(this)((/** @type {() => void} */ done) => {
27
32
  try {
28
33
  // save the page dict for this app
29
34
  if (this.appDict[appIdKey]) {
@@ -34,7 +39,7 @@ async function onPageChange (err, appIdKey, pageDict) {
34
39
  } else {
35
40
  // we have a pre-existing pageDict
36
41
  if (_.isEqual(this.appDict[appIdKey].pageArray, pageArray)) {
37
- log.debug(`Received page change notice for app '${appIdKey}' ` +
42
+ this.log.debug(`Received page change notice for app '${appIdKey}' ` +
38
43
  `but the listing has not changed. Ignoring.`);
39
44
  return done();
40
45
  }
@@ -53,7 +58,7 @@ async function onPageChange (err, appIdKey, pageDict) {
53
58
  return;
54
59
  }
55
60
 
56
- log.debug(`Page changed: ${simpleStringify(pageDict, true)}`);
61
+ this.log.debug(`Page changed: ${simpleStringify(pageDict, true)}`);
57
62
 
58
63
  this.emit(events.EVENT_PAGE_CHANGE, {
59
64
  appIdKey: appIdKey.replace('PID:', ''),
@@ -67,12 +72,12 @@ async function onPageChange (err, appIdKey, pageDict) {
67
72
  * @param {Record<string, any>} dict
68
73
  * @returns {Promise<void>}
69
74
  */
70
- async function onAppConnect (err, dict) {
75
+ export async function onAppConnect (err, dict) {
71
76
  const appIdKey = dict.WIRApplicationIdentifierKey;
72
- log.debug(`Notified that new application '${appIdKey}' has connected`);
73
- await this.useAppDictLock((/** @type {() => Void} */done) => {
77
+ this.log.debug(`Notified that new application '${appIdKey}' has connected`);
78
+ await useAppDictLock.bind(this)((/** @type {() => void} */ done) => {
74
79
  try {
75
- this.updateAppsWithDict(dict);
80
+ updateAppsWithDict.bind(this)(dict);
76
81
  } finally {
77
82
  done();
78
83
  }
@@ -85,10 +90,10 @@ async function onAppConnect (err, dict) {
85
90
  * @param {Record<string, any>} dict
86
91
  * @returns {void}
87
92
  */
88
- function onAppDisconnect (err, dict) {
93
+ export function onAppDisconnect (err, dict) {
89
94
  const appIdKey = dict.WIRApplicationIdentifierKey;
90
- log.debug(`Application '${appIdKey}' disconnected. Removing from app dictionary.`);
91
- log.debug(`Current app is '${this.appIdKey}'`);
95
+ this.log.debug(`Application '${appIdKey}' disconnected. Removing from app dictionary.`);
96
+ this.log.debug(`Current app is '${this.appIdKey}'`);
92
97
 
93
98
  // get rid of the entry in our app dictionary,
94
99
  // since it is no longer available
@@ -96,13 +101,13 @@ function onAppDisconnect (err, dict) {
96
101
 
97
102
  // if the disconnected app is the one we are connected to, try to find another
98
103
  if (this.appIdKey === appIdKey) {
99
- log.debug(`No longer have app id. Attempting to find new one.`);
104
+ this.log.debug(`No longer have app id. Attempting to find new one.`);
100
105
  this.appIdKey = getDebuggerAppKey(/** @type {string} */ (this.bundleId), this.appDict);
101
106
  }
102
107
 
103
108
  if (!this.appDict) {
104
109
  // this means we no longer have any apps. what the what?
105
- log.debug('Main app disconnected. Disconnecting altogether.');
110
+ this.log.debug('Main app disconnected. Disconnecting altogether.');
106
111
  this.connected = false;
107
112
  this.emit(events.EVENT_DISCONNECT, true);
108
113
  }
@@ -114,10 +119,10 @@ function onAppDisconnect (err, dict) {
114
119
  * @param {Record<string, any>} dict
115
120
  * @returns {Promise<void>}
116
121
  */
117
- async function onAppUpdate (err, dict) {
118
- await this.useAppDictLock((done) => {
122
+ export async function onAppUpdate (err, dict) {
123
+ await useAppDictLock.bind(this)((/** @type {() => void} */ done) => {
119
124
  try {
120
- this.updateAppsWithDict(dict);
125
+ updateAppsWithDict.bind(this)(dict);
121
126
  } finally {
122
127
  done();
123
128
  }
@@ -130,9 +135,9 @@ async function onAppUpdate (err, dict) {
130
135
  * @param {Record<string, any>} drivers
131
136
  * @returns {void}
132
137
  */
133
- function onConnectedDriverList (err, drivers) {
138
+ export function onConnectedDriverList (err, drivers) {
134
139
  this.connectedDrivers = drivers.WIRDriverDictionaryKey;
135
- log.debug(`Received connected driver list: ${JSON.stringify(this.connectedDrivers)}`);
140
+ this.log.debug(`Received connected driver list: ${JSON.stringify(this.connectedDrivers)}`);
136
141
  }
137
142
 
138
143
  /**
@@ -141,11 +146,11 @@ function onConnectedDriverList (err, drivers) {
141
146
  * @param {Record<string, any>} state
142
147
  * @returns {void}
143
148
  */
144
- function onCurrentState (err, state) {
149
+ export function onCurrentState (err, state) {
145
150
  this.currentState = state.WIRAutomationAvailabilityKey;
146
151
  // This state changes when 'Remote Automation' in 'Settings app' > 'Safari' > 'Advanced' > 'Remote Automation' changes
147
152
  // WIRAutomationAvailabilityAvailable or WIRAutomationAvailabilityNotAvailable
148
- log.debug(`Received connected automation availability state: ${JSON.stringify(this.currentState)}`);
153
+ this.log.debug(`Received connected automation availability state: ${JSON.stringify(this.currentState)}`);
149
154
  }
150
155
 
151
156
  /**
@@ -154,8 +159,8 @@ function onCurrentState (err, state) {
154
159
  * @param {Record<string, any>} apps
155
160
  * @returns {Promise<void>}
156
161
  */
157
- async function onConnectedApplicationList (err, apps) {
158
- log.debug(`Received connected applications list: ${_.keys(apps).join(', ')}`);
162
+ export async function onConnectedApplicationList (err, apps) {
163
+ this.log.debug(`Received connected applications list: ${_.keys(apps).join(', ')}`);
159
164
 
160
165
  // translate the received information into an easier-to-manage
161
166
  // hash with app id as key, and app info as value
@@ -168,7 +173,7 @@ async function onConnectedApplicationList (err, apps) {
168
173
  newDict[id] = entry;
169
174
  }
170
175
  // update the object's list of apps
171
- await this.useAppDictLock((done) => {
176
+ await useAppDictLock.bind(this)((/** @type {() => void} */ done) => {
172
177
  try {
173
178
  _.defaults(this.appDict, newDict);
174
179
  } finally {
@@ -177,14 +182,40 @@ async function onConnectedApplicationList (err, apps) {
177
182
  });
178
183
  }
179
184
 
180
- const messageHandlers = {
181
- onPageChange,
182
- onAppConnect,
183
- onAppDisconnect,
184
- onAppUpdate,
185
- onConnectedDriverList,
186
- onCurrentState,
187
- onConnectedApplicationList,
188
- };
189
-
190
- export default messageHandlers;
185
+ /**
186
+ * @this {import('../remote-debugger').RemoteDebugger}
187
+ * @param {(done: () => any) => any} fn
188
+ * @returns {Promise<any>}
189
+ */
190
+ async function useAppDictLock (fn) {
191
+ return await this._lock.acquire('appDict', fn);
192
+ }
193
+
194
+
195
+ /**
196
+ *
197
+ * @this {import('../remote-debugger').RemoteDebugger}
198
+ * @param {import('@appium/types').StringRecord} dict
199
+ * @returns {void}
200
+ */
201
+ function updateAppsWithDict (dict) {
202
+ // get the dictionary entry into a nice form, and add it to the
203
+ // application dictionary
204
+ this.appDict = this.appDict || {};
205
+ let [id, entry] = appInfoFromDict(dict);
206
+ if (this.appDict[id]) {
207
+ // preserve the page dictionary for this entry
208
+ entry.pageArray = this.appDict[id].pageArray;
209
+ }
210
+ this.appDict[id] = entry;
211
+
212
+ // add a promise to get the page dictionary
213
+ if (_.isUndefined(entry.pageArray)) {
214
+ entry.pageArray = deferredPromise();
215
+ }
216
+
217
+ // try to get the app id from our connected apps
218
+ if (!this.appIdKey) {
219
+ this.appIdKey = getDebuggerAppKey(/** @type {string} */ (this.bundleId), this.appDict);
220
+ }
221
+ }
@@ -0,0 +1,128 @@
1
+ import { checkParams } from '../utils';
2
+ import B from 'bluebird';
3
+
4
+ const SAFARI_BUNDLE_ID = 'com.apple.mobilesafari';
5
+ const GARBAGE_COLLECT_TIMEOUT_MS = 5000;
6
+
7
+ /**
8
+ * @this {import('../remote-debugger').RemoteDebugger}
9
+ * @returns {Promise<void>}
10
+ */
11
+ export async function launchSafari () {
12
+ await this.requireRpcClient().send('launchApplication', {
13
+ bundleId: SAFARI_BUNDLE_ID
14
+ });
15
+ }
16
+
17
+ /**
18
+ * @this {import('../remote-debugger').RemoteDebugger}
19
+ * @param {(event: import('@appium/types').StringRecord) => any} fn
20
+ * @returns {Promise<any>}
21
+ */
22
+ export async function startTimeline (fn) {
23
+ this.log.debug('Starting to record the timeline');
24
+ this.requireRpcClient().on('Timeline.eventRecorded', fn);
25
+ return await this.requireRpcClient().send('Timeline.start', {
26
+ appIdKey: this.appIdKey,
27
+ pageIdKey: this.pageIdKey,
28
+ });
29
+ }
30
+
31
+ /**
32
+ * @this {import('../remote-debugger').RemoteDebugger}
33
+ * @returns {Promise<any>}
34
+ */
35
+ export async function stopTimeline () {
36
+ this.log.debug('Stopping to record the timeline');
37
+ await this.requireRpcClient().send('Timeline.stop', {
38
+ appIdKey: this.appIdKey,
39
+ pageIdKey: this.pageIdKey,
40
+ });
41
+ }
42
+
43
+ /**
44
+ * @this {import('../remote-debugger').RemoteDebugger}
45
+ * @param {(event: import('@appium/types').StringRecord) => any} listener
46
+ * @returns {void}
47
+ */
48
+ export function startConsole (listener) {
49
+ this.log.debug('Starting to listen for JavaScript console');
50
+ this.addClientEventListener('Console.messageAdded', listener);
51
+ this.addClientEventListener('Console.messageRepeatCountUpdated', listener);
52
+ }
53
+
54
+ /**
55
+ * @this {import('../remote-debugger').RemoteDebugger}
56
+ * @returns {void}
57
+ */
58
+ export function stopConsole () {
59
+ this.log.debug('Stopping to listen for JavaScript console');
60
+ this.removeClientEventListener('Console.messageAdded');
61
+ this.removeClientEventListener('Console.messageRepeatCountUpdated');
62
+ }
63
+
64
+ /**
65
+ * @this {import('../remote-debugger').RemoteDebugger}
66
+ * @param {(event: import('@appium/types').StringRecord) => any} listener
67
+ * @returns {void}
68
+ */
69
+ export function startNetwork (listener) {
70
+ this.log.debug('Starting to listen for network events');
71
+ this.addClientEventListener('NetworkEvent', listener);
72
+ }
73
+
74
+ /**
75
+ * @this {import('../remote-debugger').RemoteDebugger}
76
+ * @returns {void}
77
+ */
78
+ export function stopNetwork () {
79
+ this.log.debug('Stopping to listen for network events');
80
+ this.removeClientEventListener('NetworkEvent');
81
+ }
82
+
83
+ // Potentially this does not work for mobile safari
84
+ /**
85
+ * @this {import('../remote-debugger').RemoteDebugger}
86
+ * @param {string} value
87
+ * @returns {Promise<any>}
88
+ */
89
+ export async function overrideUserAgent (value) {
90
+ this.log.debug('Setting overrideUserAgent');
91
+ return await this.requireRpcClient().send('Page.overrideUserAgent', {
92
+ appIdKey: this.appIdKey,
93
+ pageIdKey: this.pageIdKey,
94
+ value
95
+ });
96
+ }
97
+
98
+ /**
99
+ * @this {import('../remote-debugger').RemoteDebugger}
100
+ * @param {number} [timeoutMs=GARBAGE_COLLECT_TIMEOUT_MS]
101
+ * @returns {Promise<void>}
102
+ */
103
+ export async function garbageCollect (timeoutMs = GARBAGE_COLLECT_TIMEOUT_MS) {
104
+ this.log.debug(`Garbage collecting with ${timeoutMs}ms timeout`);
105
+
106
+ try {
107
+ checkParams({appIdKey: this.appIdKey, pageIdKey: this.pageIdKey});
108
+ } catch (err) {
109
+ this.log.debug(`Unable to collect garbage at this time`);
110
+ return;
111
+ }
112
+
113
+ try {
114
+ await B.resolve(this.requireRpcClient().send(
115
+ 'Heap.gc', {
116
+ appIdKey: this.appIdKey,
117
+ pageIdKey: this.pageIdKey,
118
+ })
119
+ ).timeout(timeoutMs);
120
+ this.log.debug(`Garbage collection successful`);
121
+ } catch (e) {
122
+ if (e instanceof B.TimeoutError) {
123
+ this.log.debug(`Garbage collection timed out after ${timeoutMs}ms`);
124
+ } else {
125
+ this.log.debug(`Unable to collect garbage: ${e.message}`);
126
+ }
127
+ }
128
+ }
@@ -1,4 +1,3 @@
1
- import log from '../logger';
2
1
  import { checkParams } from '../utils';
3
2
  import events from './events';
4
3
  import { timing, util } from '@appium/support';
@@ -25,7 +24,7 @@ const PAGE_LOAD_STRATEGY = {
25
24
  * @this {import('../remote-debugger').RemoteDebugger}
26
25
  * @returns {void}
27
26
  */
28
- function frameDetached () {
27
+ export function frameDetached () {
29
28
  this.emit(events.EVENT_FRAMES_DETACHED);
30
29
  }
31
30
 
@@ -33,8 +32,8 @@ function frameDetached () {
33
32
  * @this {import('../remote-debugger').RemoteDebugger}
34
33
  * @returns {void}
35
34
  */
36
- function cancelPageLoad () {
37
- log.debug('Unregistering from page readiness notifications');
35
+ export function cancelPageLoad () {
36
+ this.log.debug('Unregistering from page readiness notifications');
38
37
  this.pageLoading = false;
39
38
  if (this.pageLoadDelay) {
40
39
  this.pageLoadDelay.cancel();
@@ -48,7 +47,7 @@ function cancelPageLoad () {
48
47
  * @param {string} readyState
49
48
  * @returns {boolean}
50
49
  */
51
- function isPageLoadingCompleted (readyState) {
50
+ export function isPageLoadingCompleted (readyState) {
52
51
  const _pageLoadStrategy = _.toLower(this.pageLoadStrategy);
53
52
  if (_pageLoadStrategy === PAGE_LOAD_STRATEGY.NONE) {
54
53
  return true;
@@ -65,14 +64,14 @@ function isPageLoadingCompleted (readyState) {
65
64
 
66
65
  /**
67
66
  * @this {import('../remote-debugger').RemoteDebugger}
68
- * @param {timing.Timer|null|undefined} startPageLoadTimer
67
+ * @param {timing.Timer?} [startPageLoadTimer]
69
68
  * @returns {Promise<void>}
70
69
  */
71
- async function waitForDom (startPageLoadTimer) {
72
- log.debug('Waiting for page readiness');
70
+ export async function waitForDom (startPageLoadTimer) {
71
+ this.log.debug('Waiting for page readiness');
73
72
  const readinessTimeoutMs = this.pageLoadMs || DEFAULT_PAGE_READINESS_TIMEOUT_MS;
74
73
  if (!_.isFunction(startPageLoadTimer?.getDuration)) {
75
- log.debug(`Page load timer not a timer. Creating new timer`);
74
+ this.log.debug(`Page load timer not a timer. Creating new timer`);
76
75
  startPageLoadTimer = new timing.Timer().start();
77
76
  }
78
77
 
@@ -94,7 +93,7 @@ async function waitForDom (startPageLoadTimer) {
94
93
  await B.delay(intervalMs);
95
94
  // we can get this called in the middle of trying to find a new app
96
95
  if (!this.appIdKey) {
97
- log.debug('Not connected to an application. Ignoring page readiess check');
96
+ this.log.debug('Not connected to an application. Ignoring page readiess check');
98
97
  return;
99
98
  }
100
99
  if (!isPageLoading) {
@@ -103,13 +102,13 @@ async function waitForDom (startPageLoadTimer) {
103
102
 
104
103
  if (await this.checkPageIsReady()) {
105
104
  if (isPageLoading) {
106
- log.debug(`Page is ready in ${elapsedMs}ms`);
105
+ this.log.debug(`Page is ready in ${elapsedMs}ms`);
107
106
  isPageLoading = false;
108
107
  }
109
108
  return;
110
109
  }
111
110
  if (elapsedMs > readinessTimeoutMs) {
112
- log.info(`Timed out after ${readinessTimeoutMs}ms of waiting for the page readiness. Continuing anyway`);
111
+ this.log.info(`Timed out after ${readinessTimeoutMs}ms of waiting for the page readiness. Continuing anyway`);
113
112
  isPageLoading = false;
114
113
  return;
115
114
  }
@@ -137,21 +136,21 @@ async function waitForDom (startPageLoadTimer) {
137
136
  * @param {number} [timeoutMs]
138
137
  * @returns {Promise<boolean>}
139
138
  */
140
- async function checkPageIsReady (timeoutMs) {
139
+ export async function checkPageIsReady (timeoutMs) {
141
140
  checkParams({appIdKey: this.appIdKey});
142
141
 
143
142
  const readyCmd = 'document.readyState;';
144
143
  try {
145
144
  const readyState = await B.resolve(this.execute(readyCmd, true))
146
145
  .timeout(timeoutMs ?? this.pageReadyTimeout);
147
- log.debug(`Document readyState is '${readyState}'. ` +
146
+ this.log.debug(`Document readyState is '${readyState}'. ` +
148
147
  `The pageLoadStrategy is '${this.pageLoadStrategy || PAGE_LOAD_STRATEGY.NORMAL}'`);
149
148
  return this.isPageLoadingCompleted(readyState);
150
149
  } catch (err) {
151
150
  if (!(err instanceof B.TimeoutError)) {
152
151
  throw err;
153
152
  }
154
- log.debug(`Page readiness check timed out after ${this.pageReadyTimeout}ms`);
153
+ this.log.debug(`Page readiness check timed out after ${this.pageReadyTimeout}ms`);
155
154
  return false;
156
155
  }
157
156
  }
@@ -161,11 +160,10 @@ async function checkPageIsReady (timeoutMs) {
161
160
  * @param {string} url
162
161
  * @returns {Promise<void>}
163
162
  */
164
- async function navToUrl (url) {
163
+ export async function navToUrl (url) {
165
164
  checkParams({appIdKey: this.appIdKey, pageIdKey: this.pageIdKey});
166
- if (!this.rpcClient) {
167
- throw new Error('rpcClient is undefined. Is the debugger connected?');
168
- }
165
+ const rpcClient = this.requireRpcClient();
166
+
169
167
  try {
170
168
  new URL(url);
171
169
  } catch (e) {
@@ -173,7 +171,7 @@ async function navToUrl (url) {
173
171
  }
174
172
 
175
173
  this._navigatingToPage = true;
176
- log.debug(`Navigating to new URL: '${url}'`);
174
+ this.log.debug(`Navigating to new URL: '${url}'`);
177
175
  const readinessTimeoutMs = this.pageLoadMs || DEFAULT_PAGE_READINESS_TIMEOUT_MS;
178
176
  /** @type {(() => void)|undefined} */
179
177
  let onPageLoaded;
@@ -192,7 +190,7 @@ async function navToUrl (url) {
192
190
  onPageLoadedTimeout = setTimeout(() => {
193
191
  if (isPageLoading) {
194
192
  isPageLoading = false;
195
- log.info(
193
+ this.log.info(
196
194
  `Timed out after ${start.getDuration().asMilliSeconds.toFixed(0)}ms of waiting ` +
197
195
  `for the ${url} page readiness. Continuing anyway`
198
196
  );
@@ -203,7 +201,7 @@ async function navToUrl (url) {
203
201
  onPageLoaded = () => {
204
202
  if (isPageLoading) {
205
203
  isPageLoading = false;
206
- log.debug(`The page ${url} is ready in ${start.getDuration().asMilliSeconds.toFixed(0)}ms`);
204
+ this.log.debug(`The page ${url} is ready in ${start.getDuration().asMilliSeconds.toFixed(0)}ms`);
207
205
  }
208
206
  if (onPageLoadedTimeout) {
209
207
  clearTimeout(onPageLoadedTimeout);
@@ -213,7 +211,7 @@ async function navToUrl (url) {
213
211
  return resolve();
214
212
  };
215
213
  // https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-loadEventFired
216
- this.rpcClient?.once('Page.loadEventFired', onPageLoaded);
214
+ rpcClient.once('Page.loadEventFired', onPageLoaded);
217
215
  // Pages that have no proper DOM structure do not fire the `Page.loadEventFired` event
218
216
  // so we rely on the very first event after target change, which is `onTargetProvisioned`
219
217
  // and start sending `document.readyState` requests until we either succeed or
@@ -233,9 +231,9 @@ async function navToUrl (url) {
233
231
  }
234
232
  }
235
233
  };
236
- this.rpcClient?.targetSubscriptions.once(rpcConstants.ON_TARGET_PROVISIONED_EVENT, onTargetProvisioned);
234
+ rpcClient.targetSubscriptions.once(rpcConstants.ON_TARGET_PROVISIONED_EVENT, onTargetProvisioned);
237
235
 
238
- this.rpcClient?.send('Page.navigate', {
236
+ rpcClient.send('Page.navigate', {
239
237
  url,
240
238
  appIdKey: this.appIdKey,
241
239
  pageIdKey: this.pageIdKey,
@@ -260,17 +258,17 @@ async function navToUrl (url) {
260
258
  onPageLoadedTimeout = null;
261
259
  }
262
260
  if (onTargetProvisioned) {
263
- this.rpcClient.targetSubscriptions.off(rpcConstants.ON_TARGET_PROVISIONED_EVENT, onTargetProvisioned);
261
+ rpcClient.targetSubscriptions.off(rpcConstants.ON_TARGET_PROVISIONED_EVENT, onTargetProvisioned);
264
262
  }
265
263
  if (onPageLoaded) {
266
- this.rpcClient.off('Page.loadEventFired', onPageLoaded);
264
+ rpcClient.off('Page.loadEventFired', onPageLoaded);
267
265
  }
268
266
  }
269
267
 
270
268
  // enable console logging, so we get the events (otherwise we only
271
269
  // get notified when navigating to a local page
272
270
  try {
273
- await B.resolve(this.rpcClient.send('Console.enable', {
271
+ await B.resolve(rpcClient.send('Console.enable', {
274
272
  appIdKey: this.appIdKey,
275
273
  pageIdKey: this.pageIdKey,
276
274
  }, didPageFinishLoad)).timeout(CONSOLE_ENABLEMENT_TIMEOUT_MS);
@@ -282,5 +280,3 @@ async function navToUrl (url) {
282
280
  throw err;
283
281
  }
284
282
  }
285
-
286
- export default {frameDetached, cancelPageLoad, waitForDom, checkPageIsReady, navToUrl, isPageLoadingCompleted};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Capture a rect of the page or by default the viewport
3
+ * @this {import('../remote-debugger').RemoteDebugger}
4
+ * @param {ScreenshotCaptureOptions} [opts={}] if rect is null capture the whole
5
+ * coordinate system else capture the rect in the given coordinateSystem
6
+ * @returns {Promise<string>} a base64 encoded string of the screenshot
7
+ */
8
+ export async function captureScreenshot(opts = {}) {
9
+ const {rect = null, coordinateSystem = 'Viewport'} = opts;
10
+ this.log.debug('Capturing screenshot');
11
+
12
+ const arect = rect ?? /** @type {import('@appium/types').Rect} */ (await this.executeAtom(
13
+ 'execute_script',
14
+ ['return {x: 0, y: 0, width: window.innerWidth, height: window.innerHeight}', []]
15
+ ));
16
+ const response = await this.requireRpcClient().send('Page.snapshotRect', {
17
+ ...arect,
18
+ appIdKey: this.appIdKey,
19
+ pageIdKey: this.pageIdKey,
20
+ coordinateSystem,
21
+ });
22
+
23
+ if (response.error) {
24
+ throw new Error(response.error);
25
+ }
26
+
27
+ return response.dataURL.replace(/^data:image\/png;base64,/, '');
28
+ }
29
+
30
+ /**
31
+ * @typedef {Object} ScreenshotCaptureOptions
32
+ * @property {import('@appium/types').Rect | null} [rect=null]
33
+ * @property {"Viewport" | "Page"} [coordinateSystem="Viewport"]
34
+ */
@@ -1,9 +1,19 @@
1
1
  import RemoteDebugger from './remote-debugger';
2
2
  import { RpcClientRealDevice } from './rpc';
3
3
 
4
+ /**
5
+ * @typedef {Object} RemoteDebuggerRealDeviceOptions
6
+ * @property {string} udid Real device UDID
7
+ */
4
8
 
5
9
  export default class RemoteDebuggerRealDevice extends RemoteDebugger {
6
- constructor (opts = {}) {
10
+ /** @type {string} */
11
+ udid;
12
+
13
+ /**
14
+ * @param {RemoteDebuggerRealDeviceOptions & import('./remote-debugger').RemoteDebuggerOptions} opts
15
+ */
16
+ constructor (opts) {
7
17
  super(opts);
8
18
 
9
19
  this.udid = opts.udid;
@@ -11,6 +21,9 @@ export default class RemoteDebuggerRealDevice extends RemoteDebugger {
11
21
  this._skippedApps = ['lockdownd'];
12
22
  }
13
23
 
24
+ /**
25
+ * @override
26
+ */
14
27
  initRpcClient () {
15
28
  this.rpcClient = new RpcClientRealDevice({
16
29
  bundleId: this.bundleId,