appium-android-driver 12.1.3 → 12.2.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/CHANGELOG.md +6 -0
- package/build/lib/commands/context/exports.d.ts +5 -0
- package/build/lib/commands/context/exports.d.ts.map +1 -1
- package/build/lib/commands/context/exports.js +17 -0
- package/build/lib/commands/context/exports.js.map +1 -1
- package/build/lib/commands/context/helpers.d.ts +7 -6
- package/build/lib/commands/context/helpers.d.ts.map +1 -1
- package/build/lib/commands/context/helpers.js +257 -252
- package/build/lib/commands/context/helpers.js.map +1 -1
- package/build/lib/driver.d.ts +6 -2
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +2 -1
- package/build/lib/driver.js.map +1 -1
- package/build/lib/execute-method-map.d.ts +3 -0
- package/build/lib/execute-method-map.d.ts.map +1 -1
- package/build/lib/execute-method-map.js +3 -0
- package/build/lib/execute-method-map.js.map +1 -1
- package/lib/commands/context/exports.js +22 -0
- package/lib/commands/context/helpers.js +322 -312
- package/lib/driver.ts +3 -1
- package/lib/execute-method-map.ts +4 -0
- package/package.json +1 -1
|
@@ -76,6 +76,246 @@ const WEBVIEW_WAIT_INTERVAL_MS = 200;
|
|
|
76
76
|
const CDP_REQ_TIMEOUT = 2000; // ms
|
|
77
77
|
const DEVTOOLS_PORTS_RANGE = [10900, 11000];
|
|
78
78
|
const DEVTOOLS_PORT_ALLOCATION_GUARD = support_1.util.getLockFileGuard(node_path_1.default.resolve(node_os_1.default.tmpdir(), 'android_devtools_port_guard'), { timeout: 7, tryRecovery: true });
|
|
79
|
+
// #region Exported Functions
|
|
80
|
+
/**
|
|
81
|
+
*
|
|
82
|
+
* @param {string} browser
|
|
83
|
+
* @returns {CHROME_BROWSER_PACKAGE_ACTIVITY[keyof CHROME_BROWSER_PACKAGE_ACTIVITY]}
|
|
84
|
+
*/
|
|
85
|
+
function getChromePkg(browser) {
|
|
86
|
+
return (exports.CHROME_BROWSER_PACKAGE_ACTIVITY[browser.toLowerCase()] ||
|
|
87
|
+
exports.CHROME_BROWSER_PACKAGE_ACTIVITY.default);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parse webview names for getContexts
|
|
91
|
+
*
|
|
92
|
+
* @this {AndroidDriver}
|
|
93
|
+
* @param {import('../types').WebviewsMapping[]} webviewsMapping
|
|
94
|
+
* @param {import('../types').GetWebviewsOpts} options
|
|
95
|
+
* @returns {string[]}
|
|
96
|
+
*/
|
|
97
|
+
function parseWebviewNames(webviewsMapping, { ensureWebviewsHavePages = true, isChromeSession = false } = {}) {
|
|
98
|
+
if (isChromeSession) {
|
|
99
|
+
return [exports.CHROMIUM_WIN];
|
|
100
|
+
}
|
|
101
|
+
/** @type {string[]} */
|
|
102
|
+
const result = [];
|
|
103
|
+
for (const { webview, pages, proc, webviewName } of webviewsMapping) {
|
|
104
|
+
if (ensureWebviewsHavePages && !pages?.length) {
|
|
105
|
+
this.log.info(`Skipping the webview '${webview}' at '${proc}' ` +
|
|
106
|
+
`since it has reported having zero pages`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (webviewName) {
|
|
110
|
+
result.push(webviewName);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
this.log.debug(`Found ${support_1.util.pluralize('webview', result.length, true)}: ${JSON.stringify(result)}`);
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get a list of available webviews mapping by introspecting processes with adb,
|
|
118
|
+
* where webviews are listed. It's possible to pass in a 'deviceSocket' arg, which
|
|
119
|
+
* limits the webview possibilities to the one running on the Chromium devtools
|
|
120
|
+
* socket we're interested in (see note on webviewsFromProcs). We can also
|
|
121
|
+
* direct this method to verify whether a particular webview process actually
|
|
122
|
+
* has any pages (if a process exists but no pages are found, Chromedriver will
|
|
123
|
+
* not actually be able to connect to it, so this serves as a guard for that
|
|
124
|
+
* strange failure mode). The strategy for checking whether any pages are
|
|
125
|
+
* active involves sending a request to the remote debug server on the device,
|
|
126
|
+
* hence it is also possible to specify the port on the host machine which
|
|
127
|
+
* should be used for this communication.
|
|
128
|
+
*
|
|
129
|
+
* @this {AndroidDriver}
|
|
130
|
+
* @param {import('../types').GetWebviewsOpts} [opts={}]
|
|
131
|
+
* @returns {Promise<import('../types').WebviewsMapping[]>}
|
|
132
|
+
*/
|
|
133
|
+
async function getWebViewsMapping({ androidDeviceSocket = null, ensureWebviewsHavePages = true, webviewDevtoolsPort = null, enableWebviewDetailsCollection = true, waitForWebviewMs = 0, } = {}) {
|
|
134
|
+
this.log.debug(`Getting a list of available webviews`);
|
|
135
|
+
if (!lodash_1.default.isNumber(waitForWebviewMs)) {
|
|
136
|
+
waitForWebviewMs = parseInt(`${waitForWebviewMs}`, 10) || 0;
|
|
137
|
+
}
|
|
138
|
+
/** @type {import('../types').WebviewsMapping[]} */
|
|
139
|
+
let webviewsMapping;
|
|
140
|
+
const timer = new support_1.timing.Timer().start();
|
|
141
|
+
do {
|
|
142
|
+
webviewsMapping = await webviewsFromProcs.bind(this)(androidDeviceSocket);
|
|
143
|
+
if (webviewsMapping.length > 0) {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
this.log.debug(`No webviews found in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
|
|
147
|
+
await (0, asyncbox_1.sleep)(WEBVIEW_WAIT_INTERVAL_MS);
|
|
148
|
+
} while (timer.getDuration().asMilliSeconds < waitForWebviewMs);
|
|
149
|
+
await collectWebviewsDetails.bind(this)(webviewsMapping, {
|
|
150
|
+
ensureWebviewsHavePages,
|
|
151
|
+
enableWebviewDetailsCollection,
|
|
152
|
+
webviewDevtoolsPort,
|
|
153
|
+
});
|
|
154
|
+
for (const webviewMapping of webviewsMapping) {
|
|
155
|
+
const { webview, info } = webviewMapping;
|
|
156
|
+
webviewMapping.webviewName = null;
|
|
157
|
+
let wvName = webview;
|
|
158
|
+
/** @type {{name: string; id: string | null} | undefined} */
|
|
159
|
+
let process;
|
|
160
|
+
if (!androidDeviceSocket) {
|
|
161
|
+
const pkgMatch = WEBVIEW_PKG_PATTERN.exec(webview);
|
|
162
|
+
try {
|
|
163
|
+
// web view name could either be suffixed with PID or the package name
|
|
164
|
+
// package names could not start with a digit
|
|
165
|
+
const pkg = pkgMatch ? pkgMatch[1] : await procFromWebview.bind(this)(webview);
|
|
166
|
+
wvName = `${exports.WEBVIEW_BASE}${pkg}`;
|
|
167
|
+
const pidMatch = WEBVIEW_PID_PATTERN.exec(webview);
|
|
168
|
+
process = {
|
|
169
|
+
name: pkg,
|
|
170
|
+
id: pidMatch ? pidMatch[1] : null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
this.log.debug(e.stack);
|
|
175
|
+
this.log.warn(e.message);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
webviewMapping.webviewName = wvName;
|
|
180
|
+
const key = (0, cache_1.toDetailsCacheKey)(this.adb, wvName);
|
|
181
|
+
if (info || process) {
|
|
182
|
+
cache_1.WEBVIEWS_DETAILS_CACHE.set(key, { info, process });
|
|
183
|
+
}
|
|
184
|
+
else if (cache_1.WEBVIEWS_DETAILS_CACHE.has(key)) {
|
|
185
|
+
cache_1.WEBVIEWS_DETAILS_CACHE.delete(key);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return webviewsMapping;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* @this {AndroidDriver}
|
|
192
|
+
* @param {import('../../driver').AndroidDriverOpts} opts
|
|
193
|
+
* @param {string} curDeviceId
|
|
194
|
+
* @param {string} [context]
|
|
195
|
+
* @returns {Promise<Chromedriver>}
|
|
196
|
+
*/
|
|
197
|
+
async function setupNewChromedriver(opts, curDeviceId, context) {
|
|
198
|
+
// @ts-ignore TODO: Remove the legacy
|
|
199
|
+
if (opts.chromeDriverPort) {
|
|
200
|
+
this.log.warn(`The 'chromeDriverPort' capability is deprecated. Please use 'chromedriverPort' instead`);
|
|
201
|
+
// @ts-ignore TODO: Remove the legacy
|
|
202
|
+
opts.chromedriverPort = opts.chromeDriverPort;
|
|
203
|
+
}
|
|
204
|
+
if (opts.chromedriverPort) {
|
|
205
|
+
this.log.debug(`Using user-specified port ${opts.chromedriverPort} for chromedriver`);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// if a single port wasn't given, we'll look for a free one
|
|
209
|
+
opts.chromedriverPort = await getChromedriverPort.bind(this)(opts.chromedriverPorts);
|
|
210
|
+
}
|
|
211
|
+
const details = context ? (0, cache_1.getWebviewDetails)(this.adb, context) : undefined;
|
|
212
|
+
if (!lodash_1.default.isEmpty(details)) {
|
|
213
|
+
this.log.debug('Passing web view details to the Chromedriver constructor: ' +
|
|
214
|
+
JSON.stringify(details, null, 2));
|
|
215
|
+
}
|
|
216
|
+
/** @type {import('appium-chromedriver').ChromedriverOpts} */
|
|
217
|
+
const chromedriverOpts = {
|
|
218
|
+
port: lodash_1.default.isNil(opts.chromedriverPort) ? undefined : String(opts.chromedriverPort),
|
|
219
|
+
executable: opts.chromedriverExecutable,
|
|
220
|
+
adb: this.adb,
|
|
221
|
+
cmdArgs: /** @type {string[] | undefined} */ (opts.chromedriverArgs),
|
|
222
|
+
verbose: !!opts.showChromedriverLog,
|
|
223
|
+
executableDir: opts.chromedriverExecutableDir,
|
|
224
|
+
mappingPath: opts.chromedriverChromeMappingFile,
|
|
225
|
+
// @ts-ignore this property exists
|
|
226
|
+
bundleId: opts.chromeBundleId,
|
|
227
|
+
useSystemExecutable: opts.chromedriverUseSystemExecutable,
|
|
228
|
+
disableBuildCheck: opts.chromedriverDisableBuildCheck,
|
|
229
|
+
// @ts-ignore this is ok
|
|
230
|
+
details,
|
|
231
|
+
isAutodownloadEnabled: isChromedriverAutodownloadEnabled.bind(this)(),
|
|
232
|
+
};
|
|
233
|
+
if (this.basePath) {
|
|
234
|
+
chromedriverOpts.reqBasePath = this.basePath;
|
|
235
|
+
}
|
|
236
|
+
const chromedriver = new appium_chromedriver_1.Chromedriver(chromedriverOpts);
|
|
237
|
+
// make sure there are chromeOptions
|
|
238
|
+
opts.chromeOptions = opts.chromeOptions || {};
|
|
239
|
+
// try out any prefixed chromeOptions,
|
|
240
|
+
// and strip the prefix
|
|
241
|
+
for (const opt of lodash_1.default.keys(opts)) {
|
|
242
|
+
if (opt.endsWith(':chromeOptions')) {
|
|
243
|
+
this?.log?.warn(`Merging '${opt}' into 'chromeOptions'. This may cause unexpected behavior`);
|
|
244
|
+
lodash_1.default.merge(opts.chromeOptions, opts[opt]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Ensure there are logging preferences
|
|
248
|
+
opts.chromeLoggingPrefs = opts.chromeLoggingPrefs ?? {};
|
|
249
|
+
// Strip the prefix and store it
|
|
250
|
+
for (const opt of lodash_1.default.keys(opts)) {
|
|
251
|
+
if (opt.endsWith(':loggingPrefs')) {
|
|
252
|
+
this.log.warn(`Merging '${opt}' into 'chromeLoggingPrefs'. This may cause unexpected behavior`);
|
|
253
|
+
lodash_1.default.merge(opts.chromeLoggingPrefs, opts[opt]);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const caps = /** @type {any} */ (createChromedriverCaps.bind(this)(opts, curDeviceId, details));
|
|
257
|
+
this.log.debug(`Before starting chromedriver, androidPackage is '${caps.chromeOptions.androidPackage}'`);
|
|
258
|
+
const sessionCaps = await chromedriver.start(caps);
|
|
259
|
+
cacheChromedriverCaps.bind(this)(sessionCaps, context);
|
|
260
|
+
return chromedriver;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* @this {AndroidDriver}
|
|
264
|
+
* @template {Chromedriver} T
|
|
265
|
+
* @param {T} chromedriver
|
|
266
|
+
* @param {string} context
|
|
267
|
+
* @returns {Promise<T>}
|
|
268
|
+
*/
|
|
269
|
+
async function setupExistingChromedriver(chromedriver, context) {
|
|
270
|
+
// check the status by sending a simple window-based command to ChromeDriver
|
|
271
|
+
// if there is an error, we want to recreate the ChromeDriver session
|
|
272
|
+
if (await chromedriver.hasWorkingWebview()) {
|
|
273
|
+
const cachedCaps = this._chromedriverCapsCache.get(context);
|
|
274
|
+
if (cachedCaps) {
|
|
275
|
+
cacheChromedriverCaps.bind(this)(cachedCaps, context);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
this.log.debug('ChromeDriver is not associated with a window. Re-initializing the session.');
|
|
280
|
+
const sessionCaps = await chromedriver.restart();
|
|
281
|
+
cacheChromedriverCaps.bind(this)(sessionCaps, context);
|
|
282
|
+
}
|
|
283
|
+
return chromedriver;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* @this {AndroidDriver}
|
|
287
|
+
* @returns {boolean}
|
|
288
|
+
*/
|
|
289
|
+
function shouldDismissChromeWelcome() {
|
|
290
|
+
return (!!this.opts.chromeOptions &&
|
|
291
|
+
lodash_1.default.isArray(this.opts.chromeOptions.args) &&
|
|
292
|
+
this.opts.chromeOptions.args.includes('--no-first-run'));
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* @this {AndroidDriver}
|
|
296
|
+
* @returns {Promise<void>}
|
|
297
|
+
*/
|
|
298
|
+
async function dismissChromeWelcome() {
|
|
299
|
+
this.log.info('Trying to dismiss Chrome welcome');
|
|
300
|
+
let activity = await this.getCurrentActivity();
|
|
301
|
+
if (activity !== 'org.chromium.chrome.browser.firstrun.FirstRunActivity') {
|
|
302
|
+
this.log.info('Chrome welcome dialog never showed up! Continuing');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
let el = await this.findElOrEls('id', 'com.android.chrome:id/terms_accept', false);
|
|
306
|
+
await this.click(/** @type {string} */ (el.ELEMENT));
|
|
307
|
+
try {
|
|
308
|
+
let el = await this.findElOrEls('id', 'com.android.chrome:id/negative_button', false);
|
|
309
|
+
await this.click(/** @type {string} */ (el.ELEMENT));
|
|
310
|
+
}
|
|
311
|
+
catch (e) {
|
|
312
|
+
// DO NOTHING, THIS DEVICE DIDNT LAUNCH THE SIGNIN DIALOG
|
|
313
|
+
// IT MUST BE A NON GMS DEVICE
|
|
314
|
+
this.log.warn(`This device did not show Chrome SignIn dialog, ${ /** @type {Error} */(e).message}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// #endregion
|
|
318
|
+
// #region Internal Helper Functions
|
|
79
319
|
/**
|
|
80
320
|
* @returns {Promise<number>}
|
|
81
321
|
*/
|
|
@@ -133,20 +373,11 @@ async function cdpList(host, port) {
|
|
|
133
373
|
async function cdpInfo(host, port) {
|
|
134
374
|
return cdpGetRequest(host, port, '/json/version');
|
|
135
375
|
}
|
|
136
|
-
/**
|
|
137
|
-
*
|
|
138
|
-
* @param {string} browser
|
|
139
|
-
* @returns {CHROME_BROWSER_PACKAGE_ACTIVITY[keyof CHROME_BROWSER_PACKAGE_ACTIVITY]}
|
|
140
|
-
*/
|
|
141
|
-
function getChromePkg(browser) {
|
|
142
|
-
return (exports.CHROME_BROWSER_PACKAGE_ACTIVITY[browser.toLowerCase()] ||
|
|
143
|
-
exports.CHROME_BROWSER_PACKAGE_ACTIVITY.default);
|
|
144
|
-
}
|
|
145
376
|
/**
|
|
146
377
|
* Create Chromedriver capabilities based on the provided
|
|
147
378
|
* Appium capabilities
|
|
148
379
|
*
|
|
149
|
-
* @this {
|
|
380
|
+
* @this {AndroidDriver}
|
|
150
381
|
* @param {any} opts
|
|
151
382
|
* @param {string} deviceId
|
|
152
383
|
* @param {import('../types').WebViewDetails | null} [webViewDetails]
|
|
@@ -236,37 +467,10 @@ function createChromedriverCaps(opts, deviceId, webViewDetails) {
|
|
|
236
467
|
}
|
|
237
468
|
return caps;
|
|
238
469
|
}
|
|
239
|
-
/**
|
|
240
|
-
* Parse webview names for getContexts
|
|
241
|
-
*
|
|
242
|
-
* @this {import('../../driver').AndroidDriver}
|
|
243
|
-
* @param {import('../types').WebviewsMapping[]} webviewsMapping
|
|
244
|
-
* @param {import('../types').GetWebviewsOpts} options
|
|
245
|
-
* @returns {string[]}
|
|
246
|
-
*/
|
|
247
|
-
function parseWebviewNames(webviewsMapping, { ensureWebviewsHavePages = true, isChromeSession = false } = {}) {
|
|
248
|
-
if (isChromeSession) {
|
|
249
|
-
return [exports.CHROMIUM_WIN];
|
|
250
|
-
}
|
|
251
|
-
/** @type {string[]} */
|
|
252
|
-
const result = [];
|
|
253
|
-
for (const { webview, pages, proc, webviewName } of webviewsMapping) {
|
|
254
|
-
if (ensureWebviewsHavePages && !pages?.length) {
|
|
255
|
-
this.log.info(`Skipping the webview '${webview}' at '${proc}' ` +
|
|
256
|
-
`since it has reported having zero pages`);
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
if (webviewName) {
|
|
260
|
-
result.push(webviewName);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
this.log.debug(`Found ${support_1.util.pluralize('webview', result.length, true)}: ${JSON.stringify(result)}`);
|
|
264
|
-
return result;
|
|
265
|
-
}
|
|
266
470
|
/**
|
|
267
471
|
* Allocates a local port for devtools communication
|
|
268
472
|
*
|
|
269
|
-
* @this {
|
|
473
|
+
* @this {AndroidDriver}
|
|
270
474
|
* @param {string} socketName - The remote Unix socket name
|
|
271
475
|
* @param {number?} [webviewDevtoolsPort=null] - The local port number or null to apply
|
|
272
476
|
* autodetection
|
|
@@ -308,7 +512,7 @@ async function allocateDevtoolsChannel(socketName, webviewDevtoolsPort = null) {
|
|
|
308
512
|
* No error is thrown if CDP request fails - in such case no data will be
|
|
309
513
|
* recorded into the corresponding `webviewsMapping` item.
|
|
310
514
|
*
|
|
311
|
-
* @this {
|
|
515
|
+
* @this {AndroidDriver}
|
|
312
516
|
* @param {import('../types').WebviewProps[]} webviewsMapping The current webviews mapping
|
|
313
517
|
* !!! Each item of this array gets mutated (`info`/`pages` properties get added
|
|
314
518
|
* based on the provided `opts`) if the requested details have been
|
|
@@ -369,87 +573,13 @@ async function collectWebviewsDetails(webviewsMapping, opts = {}) {
|
|
|
369
573
|
await bluebird_1.default.all(detailCollectors);
|
|
370
574
|
this.log.debug(`CDP data collection completed`);
|
|
371
575
|
}
|
|
372
|
-
/**
|
|
373
|
-
* Get a list of available webviews mapping by introspecting processes with adb,
|
|
374
|
-
* where webviews are listed. It's possible to pass in a 'deviceSocket' arg, which
|
|
375
|
-
* limits the webview possibilities to the one running on the Chromium devtools
|
|
376
|
-
* socket we're interested in (see note on webviewsFromProcs). We can also
|
|
377
|
-
* direct this method to verify whether a particular webview process actually
|
|
378
|
-
* has any pages (if a process exists but no pages are found, Chromedriver will
|
|
379
|
-
* not actually be able to connect to it, so this serves as a guard for that
|
|
380
|
-
* strange failure mode). The strategy for checking whether any pages are
|
|
381
|
-
* active involves sending a request to the remote debug server on the device,
|
|
382
|
-
* hence it is also possible to specify the port on the host machine which
|
|
383
|
-
* should be used for this communication.
|
|
384
|
-
*
|
|
385
|
-
* @this {import('../../driver').AndroidDriver}
|
|
386
|
-
* @param {import('../types').GetWebviewsOpts} [opts={}]
|
|
387
|
-
* @returns {Promise<import('../types').WebviewsMapping[]>}
|
|
388
|
-
*/
|
|
389
|
-
async function getWebViewsMapping({ androidDeviceSocket = null, ensureWebviewsHavePages = true, webviewDevtoolsPort = null, enableWebviewDetailsCollection = true, waitForWebviewMs = 0, } = {}) {
|
|
390
|
-
this.log.debug(`Getting a list of available webviews`);
|
|
391
|
-
if (!lodash_1.default.isNumber(waitForWebviewMs)) {
|
|
392
|
-
waitForWebviewMs = parseInt(`${waitForWebviewMs}`, 10) || 0;
|
|
393
|
-
}
|
|
394
|
-
/** @type {import('../types').WebviewsMapping[]} */
|
|
395
|
-
let webviewsMapping;
|
|
396
|
-
const timer = new support_1.timing.Timer().start();
|
|
397
|
-
do {
|
|
398
|
-
webviewsMapping = await webviewsFromProcs.bind(this)(androidDeviceSocket);
|
|
399
|
-
if (webviewsMapping.length > 0) {
|
|
400
|
-
break;
|
|
401
|
-
}
|
|
402
|
-
this.log.debug(`No webviews found in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
|
|
403
|
-
await (0, asyncbox_1.sleep)(WEBVIEW_WAIT_INTERVAL_MS);
|
|
404
|
-
} while (timer.getDuration().asMilliSeconds < waitForWebviewMs);
|
|
405
|
-
await collectWebviewsDetails.bind(this)(webviewsMapping, {
|
|
406
|
-
ensureWebviewsHavePages,
|
|
407
|
-
enableWebviewDetailsCollection,
|
|
408
|
-
webviewDevtoolsPort,
|
|
409
|
-
});
|
|
410
|
-
for (const webviewMapping of webviewsMapping) {
|
|
411
|
-
const { webview, info } = webviewMapping;
|
|
412
|
-
webviewMapping.webviewName = null;
|
|
413
|
-
let wvName = webview;
|
|
414
|
-
/** @type {{name: string; id: string | null} | undefined} */
|
|
415
|
-
let process;
|
|
416
|
-
if (!androidDeviceSocket) {
|
|
417
|
-
const pkgMatch = WEBVIEW_PKG_PATTERN.exec(webview);
|
|
418
|
-
try {
|
|
419
|
-
// web view name could either be suffixed with PID or the package name
|
|
420
|
-
// package names could not start with a digit
|
|
421
|
-
const pkg = pkgMatch ? pkgMatch[1] : await procFromWebview.bind(this)(webview);
|
|
422
|
-
wvName = `${exports.WEBVIEW_BASE}${pkg}`;
|
|
423
|
-
const pidMatch = WEBVIEW_PID_PATTERN.exec(webview);
|
|
424
|
-
process = {
|
|
425
|
-
name: pkg,
|
|
426
|
-
id: pidMatch ? pidMatch[1] : null,
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
catch (e) {
|
|
430
|
-
this.log.debug(e.stack);
|
|
431
|
-
this.log.warn(e.message);
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
webviewMapping.webviewName = wvName;
|
|
436
|
-
const key = (0, cache_1.toDetailsCacheKey)(this.adb, wvName);
|
|
437
|
-
if (info || process) {
|
|
438
|
-
cache_1.WEBVIEWS_DETAILS_CACHE.set(key, { info, process });
|
|
439
|
-
}
|
|
440
|
-
else if (cache_1.WEBVIEWS_DETAILS_CACHE.has(key)) {
|
|
441
|
-
cache_1.WEBVIEWS_DETAILS_CACHE.delete(key);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return webviewsMapping;
|
|
445
|
-
}
|
|
446
576
|
/**
|
|
447
577
|
* Take a webview name like WEBVIEW_4296 and use 'adb shell ps' to figure out
|
|
448
578
|
* which app package is associated with that webview. One of the reasons we
|
|
449
579
|
* want to do this is to make sure we're listing webviews for the actual AUT,
|
|
450
580
|
* not some other running app
|
|
451
581
|
*
|
|
452
|
-
* @this {
|
|
582
|
+
* @this {AndroidDriver}
|
|
453
583
|
* @param {string} webview
|
|
454
584
|
* @returns {Promise<string>}
|
|
455
585
|
*/
|
|
@@ -471,7 +601,7 @@ async function procFromWebview(webview) {
|
|
|
471
601
|
* See https://cs.chromium.org/chromium/src/chrome/browser/devtools/device/android_device_info_query.cc
|
|
472
602
|
* for more details
|
|
473
603
|
*
|
|
474
|
-
* @this {
|
|
604
|
+
* @this {AndroidDriver}
|
|
475
605
|
* @returns {Promise<string[]>} a list of matching webview socket names (including the leading '@')
|
|
476
606
|
*/
|
|
477
607
|
async function getPotentialWebviewProcs() {
|
|
@@ -517,7 +647,7 @@ async function getPotentialWebviewProcs() {
|
|
|
517
647
|
* that socket name (this is for apps which embed Chromium, which isn't the
|
|
518
648
|
* same as chrome-backed webviews).
|
|
519
649
|
*
|
|
520
|
-
* @this {
|
|
650
|
+
* @this {AndroidDriver}
|
|
521
651
|
* @param {string?} [deviceSocket=null] - the explictly-named device socket to use
|
|
522
652
|
* @returns {Promise<import('../types').WebviewProc[]>}
|
|
523
653
|
*/
|
|
@@ -555,7 +685,7 @@ async function webviewsFromProcs(deviceSocket = null) {
|
|
|
555
685
|
return webviews;
|
|
556
686
|
}
|
|
557
687
|
/**
|
|
558
|
-
* @this {
|
|
688
|
+
* @this {AndroidDriver}
|
|
559
689
|
* @param {import('../types').PortSpec} [portSpec]
|
|
560
690
|
* @returns {Promise<number>}
|
|
561
691
|
*/
|
|
@@ -599,7 +729,7 @@ async function getChromedriverPort(portSpec) {
|
|
|
599
729
|
return foundPort;
|
|
600
730
|
}
|
|
601
731
|
/**
|
|
602
|
-
* @this {
|
|
732
|
+
* @this {AndroidDriver}
|
|
603
733
|
* @returns {boolean}
|
|
604
734
|
*/
|
|
605
735
|
function isChromedriverAutodownloadEnabled() {
|
|
@@ -611,148 +741,21 @@ function isChromedriverAutodownloadEnabled() {
|
|
|
611
741
|
return false;
|
|
612
742
|
}
|
|
613
743
|
/**
|
|
614
|
-
* @this {
|
|
615
|
-
* @param {import('../../driver').AndroidDriverOpts} opts
|
|
616
|
-
* @param {string} curDeviceId
|
|
617
|
-
* @param {string} [context]
|
|
618
|
-
* @returns {Promise<Chromedriver>}
|
|
619
|
-
*/
|
|
620
|
-
async function setupNewChromedriver(opts, curDeviceId, context) {
|
|
621
|
-
// @ts-ignore TODO: Remove the legacy
|
|
622
|
-
if (opts.chromeDriverPort) {
|
|
623
|
-
this.log.warn(`The 'chromeDriverPort' capability is deprecated. Please use 'chromedriverPort' instead`);
|
|
624
|
-
// @ts-ignore TODO: Remove the legacy
|
|
625
|
-
opts.chromedriverPort = opts.chromeDriverPort;
|
|
626
|
-
}
|
|
627
|
-
if (opts.chromedriverPort) {
|
|
628
|
-
this.log.debug(`Using user-specified port ${opts.chromedriverPort} for chromedriver`);
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
// if a single port wasn't given, we'll look for a free one
|
|
632
|
-
opts.chromedriverPort = await getChromedriverPort.bind(this)(opts.chromedriverPorts);
|
|
633
|
-
}
|
|
634
|
-
const details = context ? (0, cache_1.getWebviewDetails)(this.adb, context) : undefined;
|
|
635
|
-
if (!lodash_1.default.isEmpty(details)) {
|
|
636
|
-
this.log.debug('Passing web view details to the Chromedriver constructor: ' +
|
|
637
|
-
JSON.stringify(details, null, 2));
|
|
638
|
-
}
|
|
639
|
-
/** @type {import('appium-chromedriver').ChromedriverOpts} */
|
|
640
|
-
const chromedriverOpts = {
|
|
641
|
-
port: lodash_1.default.isNil(opts.chromedriverPort) ? undefined : String(opts.chromedriverPort),
|
|
642
|
-
executable: opts.chromedriverExecutable,
|
|
643
|
-
adb: this.adb,
|
|
644
|
-
cmdArgs: /** @type {string[] | undefined} */ (opts.chromedriverArgs),
|
|
645
|
-
verbose: !!opts.showChromedriverLog,
|
|
646
|
-
executableDir: opts.chromedriverExecutableDir,
|
|
647
|
-
mappingPath: opts.chromedriverChromeMappingFile,
|
|
648
|
-
// @ts-ignore this property exists
|
|
649
|
-
bundleId: opts.chromeBundleId,
|
|
650
|
-
useSystemExecutable: opts.chromedriverUseSystemExecutable,
|
|
651
|
-
disableBuildCheck: opts.chromedriverDisableBuildCheck,
|
|
652
|
-
// @ts-ignore this is ok
|
|
653
|
-
details,
|
|
654
|
-
isAutodownloadEnabled: isChromedriverAutodownloadEnabled.bind(this)(),
|
|
655
|
-
};
|
|
656
|
-
if (this.basePath) {
|
|
657
|
-
chromedriverOpts.reqBasePath = this.basePath;
|
|
658
|
-
}
|
|
659
|
-
const chromedriver = new appium_chromedriver_1.Chromedriver(chromedriverOpts);
|
|
660
|
-
// make sure there are chromeOptions
|
|
661
|
-
opts.chromeOptions = opts.chromeOptions || {};
|
|
662
|
-
// try out any prefixed chromeOptions,
|
|
663
|
-
// and strip the prefix
|
|
664
|
-
for (const opt of lodash_1.default.keys(opts)) {
|
|
665
|
-
if (opt.endsWith(':chromeOptions')) {
|
|
666
|
-
this?.log?.warn(`Merging '${opt}' into 'chromeOptions'. This may cause unexpected behavior`);
|
|
667
|
-
lodash_1.default.merge(opts.chromeOptions, opts[opt]);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
// Ensure there are logging preferences
|
|
671
|
-
opts.chromeLoggingPrefs = opts.chromeLoggingPrefs ?? {};
|
|
672
|
-
// Strip the prefix and store it
|
|
673
|
-
for (const opt of lodash_1.default.keys(opts)) {
|
|
674
|
-
if (opt.endsWith(':loggingPrefs')) {
|
|
675
|
-
this.log.warn(`Merging '${opt}' into 'chromeLoggingPrefs'. This may cause unexpected behavior`);
|
|
676
|
-
lodash_1.default.merge(opts.chromeLoggingPrefs, opts[opt]);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
const caps = /** @type {any} */ (createChromedriverCaps.bind(this)(opts, curDeviceId, details));
|
|
680
|
-
this.log.debug(`Before starting chromedriver, androidPackage is '${caps.chromeOptions.androidPackage}'`);
|
|
681
|
-
const sessionCaps = await chromedriver.start(caps);
|
|
682
|
-
updateBidiProxyUrl.bind(this)(sessionCaps, context);
|
|
683
|
-
return chromedriver;
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* @this {import('../../driver').AndroidDriver}
|
|
687
|
-
* @template {Chromedriver} T
|
|
688
|
-
* @param {T} chromedriver
|
|
689
|
-
* @param {string} context
|
|
690
|
-
* @returns {Promise<T>}
|
|
691
|
-
*/
|
|
692
|
-
async function setupExistingChromedriver(chromedriver, context) {
|
|
693
|
-
// check the status by sending a simple window-based command to ChromeDriver
|
|
694
|
-
// if there is an error, we want to recreate the ChromeDriver session
|
|
695
|
-
if (await chromedriver.hasWorkingWebview()) {
|
|
696
|
-
const cachedBidiProxyUrl = this._bidiProxyUrlCache.get(context);
|
|
697
|
-
if (cachedBidiProxyUrl) {
|
|
698
|
-
updateBidiProxyUrl.bind(this)({ webSocketUrl: cachedBidiProxyUrl }, context);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
else {
|
|
702
|
-
this.log.debug('ChromeDriver is not associated with a window. Re-initializing the session.');
|
|
703
|
-
const sessionCaps = await chromedriver.restart();
|
|
704
|
-
updateBidiProxyUrl.bind(this)(sessionCaps, context);
|
|
705
|
-
}
|
|
706
|
-
return chromedriver;
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* @this {import('../../driver').AndroidDriver}
|
|
744
|
+
* @this {AndroidDriver}
|
|
710
745
|
* @param {Record<string, any>} sessionCaps
|
|
711
746
|
* @param {string} context
|
|
712
747
|
* @returns {void}
|
|
713
748
|
*/
|
|
714
|
-
function
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
749
|
+
function cacheChromedriverCaps(sessionCaps, context) {
|
|
750
|
+
// Store full session capabilities in cache
|
|
751
|
+
this._chromedriverCapsCache.set(context, sessionCaps);
|
|
752
|
+
if (this.opts?.chromedriverForwardBiDi
|
|
753
|
+
&& sessionCaps?.webSocketUrl
|
|
754
|
+
&& this._bidiProxyUrl !== sessionCaps.webSocketUrl) {
|
|
720
755
|
this._bidiProxyUrl = sessionCaps.webSocketUrl;
|
|
721
756
|
this.log.debug(`Updated Bidi Proxy URL to ${this._bidiProxyUrl}`);
|
|
722
757
|
}
|
|
723
758
|
}
|
|
724
|
-
/**
|
|
725
|
-
* @this {import('../../driver').AndroidDriver}
|
|
726
|
-
* @returns {boolean}
|
|
727
|
-
*/
|
|
728
|
-
function shouldDismissChromeWelcome() {
|
|
729
|
-
return (!!this.opts.chromeOptions &&
|
|
730
|
-
lodash_1.default.isArray(this.opts.chromeOptions.args) &&
|
|
731
|
-
this.opts.chromeOptions.args.includes('--no-first-run'));
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* @this {import('../../driver').AndroidDriver}
|
|
735
|
-
* @returns {Promise<void>}
|
|
736
|
-
*/
|
|
737
|
-
async function dismissChromeWelcome() {
|
|
738
|
-
this.log.info('Trying to dismiss Chrome welcome');
|
|
739
|
-
let activity = await this.getCurrentActivity();
|
|
740
|
-
if (activity !== 'org.chromium.chrome.browser.firstrun.FirstRunActivity') {
|
|
741
|
-
this.log.info('Chrome welcome dialog never showed up! Continuing');
|
|
742
|
-
return;
|
|
743
|
-
}
|
|
744
|
-
let el = await this.findElOrEls('id', 'com.android.chrome:id/terms_accept', false);
|
|
745
|
-
await this.click(/** @type {string} */ (el.ELEMENT));
|
|
746
|
-
try {
|
|
747
|
-
let el = await this.findElOrEls('id', 'com.android.chrome:id/negative_button', false);
|
|
748
|
-
await this.click(/** @type {string} */ (el.ELEMENT));
|
|
749
|
-
}
|
|
750
|
-
catch (e) {
|
|
751
|
-
// DO NOTHING, THIS DEVICE DIDNT LAUNCH THE SIGNIN DIALOG
|
|
752
|
-
// IT MUST BE A NON GMS DEVICE
|
|
753
|
-
this.log.warn(`This device did not show Chrome SignIn dialog, ${ /** @type {Error} */(e).message}`);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
759
|
/**
|
|
757
760
|
* https://github.com/puppeteer/puppeteer/issues/2242#issuecomment-544219536
|
|
758
761
|
*
|
|
@@ -764,7 +767,9 @@ function isCompatibleCdpHost(host) {
|
|
|
764
767
|
|| host.endsWith('.localhost')
|
|
765
768
|
|| Boolean(node_net_1.default.isIP(host));
|
|
766
769
|
}
|
|
770
|
+
// #endregion
|
|
767
771
|
/**
|
|
768
772
|
* @typedef {import('appium-adb').ADB} ADB
|
|
773
|
+
* @typedef {import('../../driver').AndroidDriver} AndroidDriver
|
|
769
774
|
*/
|
|
770
775
|
//# sourceMappingURL=helpers.js.map
|