Best JavaScript code snippet using appium-android-driver
webview-helpers.js
Source:webview-helpers.js
1import _ from 'lodash';2import logger from './logger';3import axios from 'axios';4import { util } from '@appium/support';5import { findAPortNotInUse } from 'portscanner';6import LRU from 'lru-cache';7import B from 'bluebird';8import path from 'path';9import os from 'os';10const NATIVE_WIN = 'NATIVE_APP';11const WEBVIEW_WIN = 'WEBVIEW';12const CHROMIUM_WIN = 'CHROMIUM';13const WEBVIEW_BASE = `${WEBVIEW_WIN}_`;14const WEBVIEW_PID_PATTERN = new RegExp(`^${WEBVIEW_BASE}(\\d+)`);15const WEBVIEW_PKG_PATTERN = new RegExp(`^${WEBVIEW_BASE}([^\\d\\s][\\w.]*)`);16const DEVTOOLS_SOCKET_PATTERN = /@[\w.]+_devtools_remote_?(\d+)?\b/;17const CROSSWALK_SOCKET_PATTERN = /@([\w.]+)_devtools_remote\b/;18const CHROMIUM_DEVTOOLS_SOCKET = 'chrome_devtools_remote';19const CHROME_PACKAGE_NAME = 'com.android.chrome';20const KNOWN_CHROME_PACKAGE_NAMES = [21 CHROME_PACKAGE_NAME,22 'com.chrome.beta',23 'com.chrome.dev',24 'com.chrome.canary',25];26const DEVTOOLS_PORTS_RANGE = [10900, 11000];27const WEBVIEWS_DETAILS_CACHE = new LRU({28 max: 100,29 updateAgeOnGet: true,30});31const CDP_REQ_TIMEOUT = 2000; // ms32const DEVTOOLS_PORT_ALLOCATION_GUARD = util.getLockFileGuard(33 path.resolve(os.tmpdir(), 'android_devtools_port_guard'),34 {timeout: 7, tryRecovery: true}35);36const helpers = {};37function toDetailsCacheKey (adb, webview) {38 return `${adb?.curDeviceId}:${webview}`;39}40/**41 * This function gets a list of android system processes and returns ones42 * that look like webviews43 * See https://cs.chromium.org/chromium/src/chrome/browser/devtools/device/android_device_info_query.cc44 * for more details45 *46 * @param {object} adb - an ADB instance47 *48 * @return {Array.<string>} - a list of matching webview socket names (including the leading '@')49 */50async function getPotentialWebviewProcs (adb) {51 const out = await adb.shell(['cat', '/proc/net/unix']);52 const names = [];53 const allMatches = [];54 for (const line of out.split('\n')) {55 // Num RefCount Protocol Flags Type St Inode Path56 const [,,, flags,, st,, sockPath] = line.trim().split(/\s+/);57 if (!sockPath) {58 continue;59 }60 if (sockPath.startsWith('@')) {61 allMatches.push(line.trim());62 }63 if (flags !== '00010000' || st !== '01') {64 continue;65 }66 if (!DEVTOOLS_SOCKET_PATTERN.test(sockPath)) {67 continue;68 }69 names.push(sockPath);70 }71 if (_.isEmpty(names)) {72 logger.debug('Found no active devtools sockets');73 if (!_.isEmpty(allMatches)) {74 logger.debug(`Other sockets are: ${JSON.stringify(allMatches, null, 2)}`);75 }76 } else {77 logger.debug(`Parsed ${names.length} active devtools ${util.pluralize('socket', names.length, false)}: ` +78 JSON.stringify(names));79 }80 // sometimes the webview process shows up multiple times per app81 return _.uniq(names);82}83/**84 * @typedef {Object} WebviewProc85 * @property {string} proc - The webview process name (as returned by86 * getPotentialWebviewProcs87 * @property {string} webview - The actual webview context name88 */89/**90 * This function retrieves a list of system processes that look like webviews,91 * and returns them along with the webview context name appropriate for it.92 * If we pass in a deviceSocket, we only attempt to find webviews which match93 * that socket name (this is for apps which embed Chromium, which isn't the94 * same as chrome-backed webviews).95 *96 * @param {object} adb - an ADB instance97 * @param {?string} deviceSocket - the explictly-named device socket to use98 *99 * @return {Array.<WebviewProc>}100 */101async function webviewsFromProcs (adb, deviceSocket = null) {102 const socketNames = await getPotentialWebviewProcs(adb);103 const webviews = [];104 for (const socketName of socketNames) {105 if (deviceSocket === CHROMIUM_DEVTOOLS_SOCKET && socketName === `@${deviceSocket}`) {106 webviews.push({107 proc: socketName,108 webview: CHROMIUM_WIN,109 });110 continue;111 }112 const socketNameMatch = DEVTOOLS_SOCKET_PATTERN.exec(socketName);113 if (!socketNameMatch) {114 continue;115 }116 const crosswalkMatch = CROSSWALK_SOCKET_PATTERN.exec(socketName);117 if (!socketNameMatch[1] && !crosswalkMatch) {118 continue;119 }120 if (deviceSocket && socketName === `@${deviceSocket}` || !deviceSocket) {121 webviews.push({122 proc: socketName,123 webview: socketNameMatch[1]124 ? `${WEBVIEW_BASE}${socketNameMatch[1]}`125 : `${WEBVIEW_BASE}${crosswalkMatch[1]}`,126 });127 }128 }129 return webviews;130}131/**132 * Allocates a local port for devtools communication133 *134 * @param {ADB} adb ADB instance135 * @param {string} socketName The remote Unix socket name136 * @param {?number} webviewDevtoolsPort The local port number or null to apply137 * autodetection138 * @returns {number} The local port number if the remote socket has been forwarded139 * successfully or `null` otherwise140 * @throws {Error} If there was an error while allocating the local port141 */142async function allocateDevtoolsPort (adb, socketName, webviewDevtoolsPort = null) {143 // socket names come with '@', but this should not be a part of the abstract144 // remote port, so remove it145 const remotePort = socketName.replace(/^@/, '');146 let [startPort, endPort] = DEVTOOLS_PORTS_RANGE;147 if (webviewDevtoolsPort) {148 endPort = webviewDevtoolsPort + (endPort - startPort);149 startPort = webviewDevtoolsPort;150 }151 logger.debug(`Forwarding remote port ${remotePort} to a local ` +152 `port in range ${startPort}..${endPort}`);153 if (!webviewDevtoolsPort) {154 logger.debug(`You could use the 'webviewDevtoolsPort' capability to customize ` +155 `the starting port number`);156 }157 return await DEVTOOLS_PORT_ALLOCATION_GUARD(async () => {158 let localPort;159 try {160 localPort = await findAPortNotInUse(startPort, endPort);161 } catch (e) {162 throw new Error(`Cannot find any free port to forward the Devtools socket ` +163 `in range ${startPort}..${endPort}. You could set the starting port number ` +164 `manually by providing the 'webviewDevtoolsPort' capability`);165 }166 await adb.adbExec(['forward', `tcp:${localPort}`, `localabstract:${remotePort}`]);167 return localPort;168 });169}170/**171 * @typedef {Object} WebviewProps172 * @property {string} proc The name of the Devtools Unix socket173 * @property {string} webview The web view alias. Looks like `WEBVIEW_`174 * prefix plus PID or package name175 * @property {?Object} info Webview information as it is retrieved by176 * /json/version CDP endpoint177 * @property {?Array<Object>} pages Webview pages list as it is retrieved by178 * /json/list CDP endpoint179 */180/**181 * @typedef {Object} DetailCollectionOptions182 * @property {?string|number} webviewDevtoolsPort The starting port to use for webview page183 * presence check (if not the default of 9222).184 * @property {?boolean} ensureWebviewsHavePages Whether to check for webview185 * pages presence186 * @property {boolean} enableWebviewDetailsCollection Whether to collect187 * web view details and send them to Chromedriver constructor, so it could188 * select a binary more precisely based on this info.189 */190/**191 * This is a wrapper for Chrome Debugger Protocol data collection.192 * No error is thrown if CDP request fails - in such case no data will be193 * recorded into the corresponding `webviewsMapping` item.194 *195 * @param {ADB} adb The ADB instance196 * @param {Array<WebviewProps>} webviewsMapping The current webviews mapping197 * !!! Each item of this array gets mutated (`info`/`pages` properties get added198 * based on the provided `opts`) if the requested details have been199 * successfully retrieved for it !!!200 * @param {DetailCollectionOptions} opts If both `ensureWebviewsHavePages` and201 * `enableWebviewDetailsCollection` properties are falsy then no details collection202 * is performed203 */204async function collectWebviewsDetails (adb, webviewsMapping, opts = {}) {205 if (_.isEmpty(webviewsMapping)) {206 return;207 }208 const {209 webviewDevtoolsPort = null,210 ensureWebviewsHavePages = null,211 enableWebviewDetailsCollection = null,212 } = opts;213 if (!ensureWebviewsHavePages) {214 logger.info(`Not checking whether webviews have active pages; use the ` +215 `'ensureWebviewsHavePages' cap to turn this check on`);216 }217 if (!enableWebviewDetailsCollection) {218 logger.info(`Not collecting web view details. Details collection might help ` +219 `to make Chromedriver initialization more precise. Use the 'enableWebviewDetailsCollection' ` +220 `cap to turn it on`);221 }222 if (!ensureWebviewsHavePages && !enableWebviewDetailsCollection) {223 return;224 }225 // Connect to each devtools socket and retrieve web view details226 logger.debug(`Collecting CDP data of ${util.pluralize('webview', webviewsMapping.length, true)}`);227 const detailCollectors = [];228 for (const item of webviewsMapping) {229 detailCollectors.push((async () => {230 let localPort;231 try {232 localPort = await allocateDevtoolsPort(adb, item.proc, webviewDevtoolsPort);233 if (enableWebviewDetailsCollection) {234 item.info = await cdpInfo(localPort);235 }236 if (ensureWebviewsHavePages) {237 item.pages = await cdpList(localPort);238 }239 } catch (e) {240 logger.debug(e);241 } finally {242 if (localPort) {243 await adb.removePortForward(localPort);244 }245 }246 })());247 }248 await B.all(detailCollectors);249 logger.debug(`CDP data collection completed`);250}251// https://chromedevtools.github.io/devtools-protocol/252async function cdpList (localPort) {253 return (await axios({254 url: `http://127.0.0.1:${localPort}/json/list`,255 timeout: CDP_REQ_TIMEOUT,256 })).data;257}258// https://chromedevtools.github.io/devtools-protocol/259async function cdpInfo (localPort) {260 return (await axios({261 url: `http://127.0.0.1:${localPort}/json/version`,262 timeout: CDP_REQ_TIMEOUT,263 })).data;264}265/**266 * Take a webview name like WEBVIEW_4296 and use 'adb shell ps' to figure out267 * which app package is associated with that webview. One of the reasons we268 * want to do this is to make sure we're listing webviews for the actual AUT,269 * not some other running app270 *271 * @param {object} adb - an ADB instance272 * @param {string} webview - a webview process name273 *274 * @returns {string} - the package name of the app running the webview275 * @throws {Error} If there was a failure while retrieving the process name276 */277helpers.procFromWebview = async function procFromWebview (adb, webview) {278 const pidMatch = WEBVIEW_PID_PATTERN.exec(webview);279 if (!pidMatch) {280 throw new Error(`Could not find PID for webview '${webview}'`);281 }282 const pid = pidMatch[1];283 logger.debug(`${webview} mapped to pid ${pid}`);284 logger.debug(`Getting process name for webview '${webview}'`);285 const pkg = await adb.getNameByPid(pid);286 logger.debug(`Got process name: '${pkg}'`);287 return pkg;288};289/**290 * Parse webview names for getContexts291 *292 * @param {Array<WebviewsMapping>} webviewsMapping See note on getWebViewsMapping293 * @param {GetWebviewsOpts} opts See note on getWebViewsMapping294 * @return {Array.<string>} - a list of webview names295 */296helpers.parseWebviewNames = function parseWebviewNames (webviewsMapping, {297 ensureWebviewsHavePages = true,298 isChromeSession = false299} = {}) {300 if (isChromeSession) {301 return [CHROMIUM_WIN];302 }303 const result = [];304 for (const {webview, pages, proc, webviewName} of webviewsMapping) {305 if (ensureWebviewsHavePages && pages?.length === 0) {306 logger.info(`Skipping the webview '${webview}' at '${proc}' ` +307 `since it has reported having zero pages`);308 continue;309 }310 if (webviewName) {311 result.push(webviewName);312 }313 }314 logger.debug(`Found ${util.pluralize('webview', result.length, true)}: ${JSON.stringify(result)}`);315 return result;316};317/**318 * @typedef {Object} GetWebviewsOpts319 * @property {string} androidDeviceSocket [null] - device socket name320 * @property {boolean} ensureWebviewsHavePages [true] - whether to check for webview321 * page presence322 * @property {number} webviewDevtoolsPort [9222] - port to use for webview page323 * presence check.324 * @property {boolean} enableWebviewDetailsCollection [true] - whether to collect325 * web view details and send them to Chromedriver constructor, so it could326 * select a binary more precisely based on this info.327 */328/**329 * @typedef {Object} WebviewsMapping330 * @property {string} proc See note on WebviewProps331 * @property {string} webview See note on WebviewProps332 * @property {?Object} info See note on WebviewProps333 * @property {?Array<Object>} pages See note on WebviewProps334 * @propery {?string} webviewName An actual webview name for switching context335 */336/**337 * Get a list of available webviews mapping by introspecting processes with adb,338 * where webviews are listed. It's possible to pass in a 'deviceSocket' arg, which339 * limits the webview possibilities to the one running on the Chromium devtools340 * socket we're interested in (see note on webviewsFromProcs). We can also341 * direct this method to verify whether a particular webview process actually342 * has any pages (if a process exists but no pages are found, Chromedriver will343 * not actually be able to connect to it, so this serves as a guard for that344 * strange failure mode). The strategy for checking whether any pages are345 * active involves sending a request to the remote debug server on the device,346 * hence it is also possible to specify the port on the host machine which347 * should be used for this communication.348 *349 * @param {object} adb - an ADB instance350 * @param {GetWebviewsOpts} opts351 *352 * @return {Array<WebviewsMapping>} webviewsMapping353 */354helpers.getWebViewsMapping = async function getWebViewsMapping (adb, {355 androidDeviceSocket = null,356 ensureWebviewsHavePages = true,357 webviewDevtoolsPort = null,358 enableWebviewDetailsCollection = true359} = {}) {360 logger.debug('Getting a list of available webviews');361 const webviewsMapping = await webviewsFromProcs(adb, androidDeviceSocket);362 await collectWebviewsDetails(adb, webviewsMapping, {363 ensureWebviewsHavePages,364 enableWebviewDetailsCollection,365 webviewDevtoolsPort,366 });367 for (const webviewMapping of webviewsMapping) {368 const {webview, info} = webviewMapping;369 webviewMapping.webviewName = null;370 let wvName = webview;371 let process = undefined;372 if (!androidDeviceSocket) {373 const pkgMatch = WEBVIEW_PKG_PATTERN.exec(webview);374 try {375 // web view name could either be suffixed with PID or the package name376 // package names could not start with a digit377 const pkg = pkgMatch ? pkgMatch[1] : await helpers.procFromWebview(adb, webview);378 wvName = `${WEBVIEW_BASE}${pkg}`;379 const pidMatch = WEBVIEW_PID_PATTERN.exec(webview);380 process = {381 name: pkg,382 id: pidMatch ? pidMatch[1] : null,383 };384 } catch (e) {385 logger.warn(e.message);386 continue;387 }388 }389 webviewMapping.webviewName = wvName;390 const key = toDetailsCacheKey(adb, wvName);391 if (info || process) {392 WEBVIEWS_DETAILS_CACHE.set(key, { info, process });393 } else if (WEBVIEWS_DETAILS_CACHE.has(key)) {394 WEBVIEWS_DETAILS_CACHE.del(key);395 }396 }397 return webviewsMapping;398};399/**400 * @typedef {Object} ProcessInfo401 * @property {string} name The process name402 * @property {?string} id The process id (if could be retrieved)403 */404/**405 * @typedef {Object} WebViewDetails406 * @property {?ProcessInfo} process - Web view process details407 * @property {Object} info - Web view details as returned by /json/version CDP endpoint, for example:408 * {409 * "Browser": "Chrome/72.0.3601.0",410 * "Protocol-Version": "1.3",411 * "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3601.0 Safari/537.36",412 * "V8-Version": "7.2.233",413 * "WebKit-Version": "537.36 (@cfede9db1d154de0468cb0538479f34c0755a0f4)",414 * "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/b0b8a4fb-bb17-4359-9533-a8d9f3908bd8"415 * }416 */417/**418 * Retrieves web view details previously cached by `getWebviews` call419 *420 * @param {ADB} adb ADB instance421 * @param {string} webview The name of the web view422 * @returns {?WebViewDetails} Either `undefined` or the recent web view details423 */424helpers.getWebviewDetails = function getWebviewDetails (adb, webview) {425 const key = toDetailsCacheKey(adb, webview);426 return WEBVIEWS_DETAILS_CACHE.get(key);427};428/**429 * Create Chrome driver capabilities based on the provided430 * Appium capabilities431 *432 * @param {Object} opts User-provided capabilities object433 * @param {string} deviceId The identifier of the Android device under test434 * @param {?WebViewDetails} webViewDetails435 * @returns {Object} The capabilities object.436 * See https://chromedriver.chromium.org/capabilities for more details.437 */438helpers.createChromedriverCaps = function createChromedriverCaps (opts, deviceId, webViewDetails) {439 const caps = { chromeOptions: {} };440 const androidPackage = opts.chromeOptions?.androidPackage441 || opts.appPackage442 || webViewDetails?.info?.['Android-Package'];443 if (androidPackage) {444 // chromedriver raises an invalid argument error when androidPackage is 'null'445 caps.chromeOptions.androidPackage = androidPackage;446 }447 if (_.isBoolean(opts.chromeUseRunningApp)) {448 caps.chromeOptions.androidUseRunningApp = opts.chromeUseRunningApp;449 }450 if (opts.chromeAndroidPackage) {451 caps.chromeOptions.androidPackage = opts.chromeAndroidPackage;452 }453 if (opts.chromeAndroidActivity) {454 caps.chromeOptions.androidActivity = opts.chromeAndroidActivity;455 }456 if (opts.chromeAndroidProcess) {457 caps.chromeOptions.androidProcess = opts.chromeAndroidProcess;458 } else if (webViewDetails?.process?.name && webViewDetails?.process?.id) {459 caps.chromeOptions.androidProcess = webViewDetails.process.name;460 }461 if (_.toLower(opts.browserName) === 'chromium-webview') {462 caps.chromeOptions.androidActivity = opts.appActivity;463 }464 if (opts.pageLoadStrategy) {465 caps.pageLoadStrategy = opts.pageLoadStrategy;466 }467 const isChrome = _.toLower(caps.chromeOptions.androidPackage) === 'chrome';468 if (_.includes(KNOWN_CHROME_PACKAGE_NAMES, caps.chromeOptions.androidPackage) || isChrome) {469 // if we have extracted package from context name, it could come in as bare470 // "chrome", and so we should make sure the details are correct, including471 // not using an activity or process id472 if (isChrome) {473 caps.chromeOptions.androidPackage = CHROME_PACKAGE_NAME;474 }475 delete caps.chromeOptions.androidActivity;476 delete caps.chromeOptions.androidProcess;477 }478 // add device id from adb479 caps.chromeOptions.androidDeviceSerial = deviceId;480 if (_.isPlainObject(opts.loggingPrefs) || _.isPlainObject(opts.chromeLoggingPrefs)) {481 if (opts.loggingPrefs) {482 logger.warn(`The 'loggingPrefs' cap is deprecated; use the 'chromeLoggingPrefs' cap instead`);483 }484 caps.loggingPrefs = opts.chromeLoggingPrefs || opts.loggingPrefs;485 }486 if (opts.enablePerformanceLogging) {487 logger.warn(`The 'enablePerformanceLogging' cap is deprecated; simply use ` +488 `the 'chromeLoggingPrefs' cap instead, with a 'performance' key set to 'ALL'`);489 const newPref = {performance: 'ALL'};490 // don't overwrite other logging prefs that have been sent in if they exist491 caps.loggingPrefs = caps.loggingPrefs492 ? Object.assign({}, caps.loggingPrefs, newPref)493 : newPref;494 }495 if (opts.chromeOptions?.Arguments) {496 // merge `Arguments` and `args`497 opts.chromeOptions.args = [...(opts.chromeOptions.args || []), ...opts.chromeOptions.Arguments];498 delete opts.chromeOptions.Arguments;499 }500 logger.debug('Precalculated Chromedriver capabilities: ' +501 JSON.stringify(caps.chromeOptions, null, 2));502 const protectedCapNames = [];503 for (const [opt, val] of _.toPairs(opts.chromeOptions)) {504 if (_.isUndefined(caps.chromeOptions[opt])) {505 caps.chromeOptions[opt] = val;506 } else {507 protectedCapNames.push(opt);508 }509 }510 if (!_.isEmpty(protectedCapNames)) {511 logger.info('The following Chromedriver capabilities cannot be overridden ' +512 'by the provided chromeOptions:');513 for (const optName of protectedCapNames) {514 logger.info(` ${optName} (${JSON.stringify(opts.chromeOptions[optName])})`);515 }516 }517 return caps;518};519export default helpers;...
context.js
Source:context.js
1import _ from 'lodash';2import Chromedriver from 'appium-chromedriver';3import PortFinder from 'portfinder';4import B from 'bluebird';5import { util } from '@appium/support';6import { errors } from '@appium/base-driver';7import {8 default as webviewHelpers,9 NATIVE_WIN, WEBVIEW_BASE, WEBVIEW_WIN, CHROMIUM_WIN, KNOWN_CHROME_PACKAGE_NAMES10} from '../webview-helpers';11import { APP_STATE } from '../android-helpers';12const CHROMEDRIVER_AUTODOWNLOAD_FEATURE = 'chromedriver_autodownload';13let commands = {}, helpers = {}, extensions = {};14/* -------------------------------15 * Actual MJSONWP command handlers16 * ------------------------------- */17commands.getCurrentContext = async function getCurrentContext () { // eslint-disable-line require-await18 // if the current context is `null`, indicating no context19 // explicitly set, it is the default context20 return this.curContext || this.defaultContextName();21};22commands.getContexts = async function getContexts () {23 const webviewsMapping = await webviewHelpers.getWebViewsMapping(this.adb, this.opts);24 return this.assignContexts(webviewsMapping);25};26commands.setContext = async function setContext (name) {27 if (!util.hasValue(name)) {28 name = this.defaultContextName();29 } else if (name === WEBVIEW_WIN) {30 // handle setContext "WEBVIEW"31 name = this.defaultWebviewName();32 }33 // if we're already in the context we want, do nothing34 if (name === this.curContext) {35 return;36 }37 const webviewsMapping = await webviewHelpers.getWebViewsMapping(this.adb, this.opts);38 const contexts = this.assignContexts(webviewsMapping);39 // if the context we want doesn't exist, fail40 if (!_.includes(contexts, name)) {41 throw new errors.NoSuchContextError();42 }43 await this.switchContext(name, webviewsMapping);44 this.curContext = name;45};46/**47 * @typedef {Object} WebviewsMapping48 * @property {string} proc The name of the Devtools Unix socket49 * @property {string} webview The web view alias. Looks like `WEBVIEW_`50 * prefix plus PID or package name51 * @property {?Object} info Webview information as it is retrieved by52 * /json/version CDP endpoint53 * @property {?Array<Object>} pages Webview pages list as it is retrieved by54 * /json/list CDP endpoint55 * @propery {?string} webviewName An actual webview name for switching context.56 * This value becomes null when failing to find a PID for a webview.57 *58 * The following json demonstrates the example of WebviewsMapping object.59 * Note that `description` in `page` can be an empty string most likely when it comes to Mobile Chrome)60 * {61 * "proc": "@webview_devtools_remote_22138",62 * "webview": "WEBVIEW_22138",63 * "info": {64 * "Android-Package": "io.appium.settings",65 * "Browser": "Chrome/74.0.3729.185",66 * "Protocol-Version": "1.3",67 * "User-Agent": "Mozilla/5.0 (Linux; Android 10; Android SDK built for x86 Build/QSR1.190920.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36",68 * "V8-Version": "7.4.288.28",69 * "WebKit-Version": "537.36 (@22955682f94ce09336197bfb8dffea991fa32f0d)",70 * "webSocketDebuggerUrl": "ws://127.0.0.1:10900/devtools/browser"71 * },72 * "pages": [73 * {74 * "description": "{\"attached\":true,\"empty\":false,\"height\":1458,\"screenX\":0,\"screenY\":336,\"visible\":true,\"width\":1080}",75 * "devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@22955682f94ce09336197bfb8dffea991fa32f0d/inspector.html?ws=127.0.0.1:10900/devtools/page/27325CC50B600D31B233F45E09487B1F",76 * "id": "27325CC50B600D31B233F45E09487B1F",77 * "title": "Releases · appium/appium · GitHub",78 * "type": "page",79 * "url": "https://github.com/appium/appium/releases",80 * "webSocketDebuggerUrl": "ws://127.0.0.1:10900/devtools/page/27325CC50B600D31B233F45E09487B1F"81 * }82 * ],83 * "webviewName": "WEBVIEW_com.io.appium.setting"84 * }85 */86/**87 * Returns a webviewsMapping based on CDP endpoints88 *89 * @return {Array<WebviewsMapping>} webviewsMapping90 */91commands.mobileGetContexts = async function mobileGetContexts () {92 const opts = {93 androidDeviceSocket: this.opts.androidDeviceSocket,94 ensureWebviewsHavePages: true,95 webviewDevtoolsPort: this.opts.webviewDevtoolsPort,96 enableWebviewDetailsCollection: true97 };98 return await webviewHelpers.getWebViewsMapping(this.adb, opts);99};100helpers.assignContexts = function assignContexts (webviewsMapping) {101 const opts = Object.assign({isChromeSession: this.isChromeSession}, this.opts);102 const webviews = webviewHelpers.parseWebviewNames(webviewsMapping, opts);103 this.contexts = [NATIVE_WIN, ...webviews];104 this.log.debug(`Available contexts: ${JSON.stringify(this.contexts)}`);105 return this.contexts;106};107helpers.switchContext = async function switchContext (name, webviewsMapping) {108 // We have some options when it comes to webviews. If we want a109 // Chromedriver webview, we can only control one at a time.110 if (this.isChromedriverContext(name)) {111 // start proxying commands directly to chromedriver112 await this.startChromedriverProxy(name, webviewsMapping);113 } else if (this.isChromedriverContext(this.curContext)) {114 // if we're moving to a non-chromedriver webview, and our current context115 // _is_ a chromedriver webview, if caps recreateChromeDriverSessions is set116 // to true then kill chromedriver session using stopChromedriverProxies or117 // else simply suspend proxying to the latter118 if (this.opts.recreateChromeDriverSessions) {119 this.log.debug('recreateChromeDriverSessions set to true; killing existing chromedrivers');120 await this.stopChromedriverProxies();121 } else {122 await this.suspendChromedriverProxy();123 }124 } else {125 throw new Error(`Didn't know how to handle switching to context '${name}'`);126 }127};128/* ---------------------------------129 * On-object context-related helpers130 * --------------------------------- */131// The reason this is a function and not just a constant is that both android-132// driver and selendroid-driver use this logic, and each one returns133// a different default context name134helpers.defaultContextName = function defaultContextName () {135 return NATIVE_WIN;136};137helpers.defaultWebviewName = function defaultWebviewName () {138 return WEBVIEW_BASE + this.opts.appPackage;139};140helpers.isWebContext = function isWebContext () {141 return this.curContext !== null && this.curContext !== NATIVE_WIN;142};143// Turn on proxying to an existing Chromedriver session or a new one144helpers.startChromedriverProxy = async function startChromedriverProxy (context, webviewsMapping) {145 this.log.debug(`Connecting to chrome-backed webview context '${context}'`);146 let cd;147 if (this.sessionChromedrivers[context]) {148 // in the case where we've already set up a chromedriver for a context,149 // we want to reconnect to it, not create a whole new one150 this.log.debug(`Found existing Chromedriver for context '${context}'. Using it.`);151 cd = this.sessionChromedrivers[context];152 await setupExistingChromedriver(this.log, cd);153 } else {154 let opts = _.cloneDeep(this.opts);155 opts.chromeUseRunningApp = true;156 // if requested, tell chromedriver to attach to the android package we have157 // associated with the context name, rather than the package of the AUT.158 // And turn this on by default for chrome--if chrome pops up with a webview159 // and someone wants to switch to it, we should let chromedriver connect to160 // chrome rather than staying stuck on the AUT161 if (opts.extractChromeAndroidPackageFromContextName || context === `${WEBVIEW_BASE}chrome`) {162 let androidPackage = context.match(`${WEBVIEW_BASE}(.+)`);163 if (androidPackage && androidPackage.length > 0) {164 opts.chromeAndroidPackage = androidPackage[1];165 }166 if (!opts.extractChromeAndroidPackageFromContextName) {167 if (_.has(this.opts, 'enableWebviewDetailsCollection') && !this.opts.enableWebviewDetailsCollection) {168 // When enableWebviewDetailsCollection capability is explicitly disabled, try to identify169 // chromeAndroidPackage based on contexts, known chrome variant packages and queryAppState result170 // since webviewsMapping does not have info object171 const contexts = webviewsMapping.map((wm) => wm.webviewName);172 for (const knownPackage of KNOWN_CHROME_PACKAGE_NAMES) {173 if (_.includes(contexts, `${WEBVIEW_BASE}${knownPackage}`)) {174 continue;175 }176 const appState = await this.queryAppState(knownPackage);177 if (_.includes([APP_STATE.RUNNING_IN_BACKGROUND, APP_STATE.RUNNING_IN_FOREGROUND], appState)) {178 opts.chromeAndroidPackage = knownPackage;179 this.log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +180 `for context '${context}' by querying states of Chrome app packages`);181 break;182 }183 }184 } else {185 for (const wm of webviewsMapping) {186 if (wm.webviewName === context && _.has(wm?.info, 'Android-Package')) {187 opts.chromeAndroidPackage = wm.info['Android-Package'];188 this.log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +189 `for context '${context}' by CDP`);190 break;191 }192 }193 }194 }195 }196 cd = await this.setupNewChromedriver(opts, this.adb.curDeviceId, this.adb, context);197 // bind our stop/exit handler, passing in context so we know which198 // one stopped unexpectedly199 cd.on(Chromedriver.EVENT_CHANGED, (msg) => {200 if (msg.state === Chromedriver.STATE_STOPPED) {201 this.onChromedriverStop(context);202 }203 });204 // save the chromedriver object under the context205 this.sessionChromedrivers[context] = cd;206 }207 // hook up the local variables so we can proxy this biz208 this.chromedriver = cd;209 this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);210 this.proxyCommand = this.chromedriver.jwproxy.command.bind(this.chromedriver.jwproxy);211 this.jwpProxyActive = true;212};213// Stop proxying to any Chromedriver214helpers.suspendChromedriverProxy = function suspendChromedriverProxy () {215 this.chromedriver = null;216 this.proxyReqRes = null;217 this.proxyCommand = null;218 this.jwpProxyActive = false;219};220// Handle an out-of-band Chromedriver stop event221helpers.onChromedriverStop = async function onChromedriverStop (context) {222 this.log.warn(`Chromedriver for context ${context} stopped unexpectedly`);223 if (context === this.curContext) {224 // we exited unexpectedly while automating the current context and so want225 // to shut down the session and respond with an error226 let err = new Error('Chromedriver quit unexpectedly during session');227 await this.startUnexpectedShutdown(err);228 } else {229 // if a Chromedriver in the non-active context barfs, we don't really230 // care, we'll just make a new one next time we need the context.231 this.log.warn("Chromedriver quit unexpectedly, but it wasn't the active " +232 'context, ignoring');233 delete this.sessionChromedrivers[context];234 }235};236// Intentionally stop all the chromedrivers currently active, and ignore237// their exit events238helpers.stopChromedriverProxies = async function stopChromedriverProxies () {239 this.suspendChromedriverProxy(); // make sure we turn off the proxy flag240 for (let context of _.keys(this.sessionChromedrivers)) {241 let cd = this.sessionChromedrivers[context];242 this.log.debug(`Stopping chromedriver for context ${context}`);243 // stop listening for the stopped state event244 cd.removeAllListeners(Chromedriver.EVENT_CHANGED);245 try {246 await cd.stop();247 } catch (err) {248 this.log.warn(`Error stopping Chromedriver: ${err.message}`);249 }250 delete this.sessionChromedrivers[context];251 }252};253helpers.isChromedriverContext = function isChromedriverContext (viewName) {254 return _.includes(viewName, WEBVIEW_WIN) || viewName === CHROMIUM_WIN;255};256helpers.shouldDismissChromeWelcome = function shouldDismissChromeWelcome () {257 return !!this.opts.chromeOptions &&258 _.isArray(this.opts.chromeOptions.args) &&259 this.opts.chromeOptions.args.includes('--no-first-run');260};261helpers.dismissChromeWelcome = async function dismissChromeWelcome () {262 this.log.info('Trying to dismiss Chrome welcome');263 let activity = await this.getCurrentActivity();264 if (activity !== 'org.chromium.chrome.browser.firstrun.FirstRunActivity') {265 this.log.info('Chrome welcome dialog never showed up! Continuing');266 return;267 }268 let el = await this.findElOrEls('id', 'com.android.chrome:id/terms_accept', false);269 await this.click(el.ELEMENT);270 try {271 let el = await this.findElOrEls('id', 'com.android.chrome:id/negative_button', false);272 await this.click(el.ELEMENT);273 } catch (e) {274 // DO NOTHING, THIS DEVICE DIDNT LAUNCH THE SIGNIN DIALOG275 // IT MUST BE A NON GMS DEVICE276 this.log.warn(`This device did not show Chrome SignIn dialog, ${e.message}`);277 }278};279helpers.startChromeSession = async function startChromeSession () {280 this.log.info('Starting a chrome-based browser session');281 let opts = _.cloneDeep(this.opts);282 const knownPackages = [283 'org.chromium.chrome.shell',284 'com.android.chrome',285 'com.chrome.beta',286 'org.chromium.chrome',287 'org.chromium.webview_shell',288 ];289 if (_.includes(knownPackages, this.opts.appPackage)) {290 opts.chromeBundleId = this.opts.appPackage;291 } else {292 opts.chromeAndroidActivity = this.opts.appActivity;293 }294 this.chromedriver = await this.setupNewChromedriver(opts, this.adb.curDeviceId, this.adb);295 this.chromedriver.on(Chromedriver.EVENT_CHANGED, (msg) => {296 if (msg.state === Chromedriver.STATE_STOPPED) {297 this.onChromedriverStop(CHROMIUM_WIN);298 }299 });300 // Now that we have a Chrome session, we ensure that the context is301 // appropriately set and that this chromedriver is added to the list302 // of session chromedrivers so we can switch back and forth303 this.curContext = CHROMIUM_WIN;304 this.sessionChromedrivers[CHROMIUM_WIN] = this.chromedriver;305 this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);306 this.proxyCommand = this.chromedriver.jwproxy.command.bind(this.chromedriver.jwproxy);307 this.jwpProxyActive = true;308 if (this.shouldDismissChromeWelcome()) {309 // dismiss Chrome welcome dialog310 await this.dismissChromeWelcome();311 }312};313/* --------------------------314 * Internal library functions315 * -------------------------- */316async function setupExistingChromedriver (log, chromedriver) {317 // check the status by sending a simple window-based command to ChromeDriver318 // if there is an error, we want to recreate the ChromeDriver session319 if (!await chromedriver.hasWorkingWebview()) {320 log.debug('ChromeDriver is not associated with a window. ' +321 'Re-initializing the session.');322 await chromedriver.restart();323 }324 return chromedriver;325}326/**327 * Find a free port to have Chromedriver listen on.328 *329 * @param {array} portSpec - Array which is a list of ports. A list item may330 * also itself be an array of length 2 specifying a start and end port of331 * a range. Some valid port specs:332 * - [8000, 8001, 8002]333 * - [[8000, 8005]]334 * - [8000, [9000, 9100]]335 * @param {Object?} log Logger instance336 *337 * @return {number} A free port338 */339async function getChromedriverPort (portSpec, log = null) {340 const getPort = B.promisify(PortFinder.getPort, {context: PortFinder});341 // if the user didn't give us any specific information about chromedriver342 // port ranges, just find any free port343 if (!portSpec) {344 const port = await getPort();345 log?.debug(`A port was not given, using random free port: ${port}`);346 return port;347 }348 // otherwise find the free port based on a list or range provided by the user349 log?.debug(`Finding a free port for chromedriver using spec ${JSON.stringify(portSpec)}`);350 let foundPort = null;351 for (const potentialPort of portSpec) {352 let port, stopPort;353 if (_.isArray(potentialPort)) {354 ([port, stopPort] = potentialPort);355 } else {356 port = parseInt(potentialPort, 10); // ensure we have a number and not a string357 stopPort = port;358 }359 try {360 log?.debug(`Checking port range ${port}:${stopPort}`);361 foundPort = await getPort({port, stopPort});362 break;363 } catch (e) {364 log?.debug(`Nothing in port range ${port}:${stopPort} was available`);365 }366 }367 if (foundPort === null) {368 throw new Error(`Could not find a free port for chromedriver using ` +369 `chromedriverPorts spec ${JSON.stringify(portSpec)}`);370 }371 log?.debug(`Using free port ${foundPort} for chromedriver`);372 return foundPort;373}374helpers.isChromedriverAutodownloadEnabled = function isChromedriverAutodownloadEnabled () {375 if (this.isFeatureEnabled(CHROMEDRIVER_AUTODOWNLOAD_FEATURE)) {376 return true;377 }378 this?.log?.debug(`Automated Chromedriver download is disabled. ` +379 `Use '${CHROMEDRIVER_AUTODOWNLOAD_FEATURE}' server feature to enable it`);380 return false;381};382helpers.setupNewChromedriver = async function setupNewChromedriver (opts, curDeviceId, adb, context = null) {383 if (opts.chromeDriverPort) {384 this?.log?.warn(`The 'chromeDriverPort' capability is deprecated. Please use 'chromedriverPort' instead`);385 opts.chromedriverPort = opts.chromeDriverPort;386 }387 if (opts.chromedriverPort) {388 this?.log?.debug(`Using user-specified port ${opts.chromedriverPort} for chromedriver`);389 } else {390 // if a single port wasn't given, we'll look for a free one391 opts.chromedriverPort = await getChromedriverPort(opts.chromedriverPorts, this?.log);392 }393 const details = context ? webviewHelpers.getWebviewDetails(adb, context) : undefined;394 if (!_.isEmpty(details)) {395 this?.log?.debug('Passing web view details to the Chromedriver constructor: ' +396 JSON.stringify(details, null, 2));397 }398 const chromedriver = new Chromedriver({399 port: opts.chromedriverPort,400 executable: opts.chromedriverExecutable,401 adb,402 cmdArgs: opts.chromedriverArgs,403 verbose: !!opts.showChromedriverLog,404 executableDir: opts.chromedriverExecutableDir,405 mappingPath: opts.chromedriverChromeMappingFile,406 bundleId: opts.chromeBundleId,407 useSystemExecutable: opts.chromedriverUseSystemExecutable,408 disableBuildCheck: opts.chromedriverDisableBuildCheck,409 details,410 isAutodownloadEnabled: this?.isChromedriverAutodownloadEnabled?.()411 });412 // make sure there are chromeOptions413 opts.chromeOptions = opts.chromeOptions || {};414 // try out any prefixed chromeOptions,415 // and strip the prefix416 for (const opt of _.keys(opts)) {417 if (opt.endsWith(':chromeOptions')) {418 this?.log?.warn(`Merging '${opt}' into 'chromeOptions'. This may cause unexpected behavior`);419 _.merge(opts.chromeOptions, opts[opt]);420 }421 }422 const caps = webviewHelpers.createChromedriverCaps(opts, curDeviceId, details);423 this?.log?.debug(`Before starting chromedriver, androidPackage is '${caps.chromeOptions.androidPackage}'`);424 await chromedriver.start(caps);425 return chromedriver;426};427const setupNewChromedriver = helpers.setupNewChromedriver;428Object.assign(extensions, commands, helpers);429export { commands, helpers, setupNewChromedriver };...
context-specs.js
Source:context-specs.js
1import chai from 'chai';2import chaiAsPromised from 'chai-as-promised';3import sinon from 'sinon';4import { default as webviewHelpers,5 NATIVE_WIN, WEBVIEW_BASE, WEBVIEW_WIN, CHROMIUM_WIN } from '../../../lib/webview-helpers';6import { setupNewChromedriver } from '../../../lib/commands/context';7import AndroidDriver from '../../../lib/driver';8import Chromedriver from 'appium-chromedriver';9import PortFinder from 'portfinder';10import { errors } from '@appium/base-driver';11let driver;12let stubbedChromedriver;13let sandbox = sinon.createSandbox();14let expect = chai.expect;15chai.should();16chai.use(chaiAsPromised);17describe('Context', function () {18 beforeEach(function () {19 sandbox.stub(PortFinder, 'getPort').callsFake(function (cb) { // eslint-disable-line promise/prefer-await-to-callbacks20 return cb(null, 4444); // eslint-disable-line promise/prefer-await-to-callbacks21 });22 driver = new AndroidDriver();23 driver.adb = sandbox.stub();24 driver.adb.curDeviceId = 'device_id';25 driver.adb.getAdbServerPort = sandbox.stub().returns(5555);26 sandbox.stub(Chromedriver.prototype, 'restart');27 sandbox.stub(Chromedriver.prototype, 'start');28 sandbox.stub(Chromedriver.prototype.proxyReq, 'bind').returns('proxy');29 stubbedChromedriver = sinon.stub();30 stubbedChromedriver.jwproxy = sinon.stub();31 stubbedChromedriver.jwproxy.command = sinon.stub();32 stubbedChromedriver.jwproxy.command.bind = sinon.stub();33 stubbedChromedriver.proxyReq = sinon.stub();34 stubbedChromedriver.proxyReq.bind = sinon.stub();35 stubbedChromedriver.restart = sinon.stub();36 stubbedChromedriver.stop = sandbox.stub().throws();37 stubbedChromedriver.removeAllListeners = sandbox.stub();38 });39 afterEach(function () {40 sandbox.restore();41 });42 describe('getCurrentContext', function () {43 it('should return current context', async function () {44 driver.curContext = 'current_context';45 await driver.getCurrentContext().should.become('current_context');46 });47 it('should return NATIVE_APP if no context is set', async function () {48 driver.curContext = null;49 await driver.getCurrentContext().should.become(NATIVE_WIN);50 });51 });52 describe('getContexts', function () {53 it('should get Chromium context where appropriate', async function () {54 sandbox.stub(webviewHelpers, 'getWebViewsMapping');55 driver = new AndroidDriver({browserName: 'Chrome'});56 expect(await driver.getContexts()).to.include(CHROMIUM_WIN);57 webviewHelpers.getWebViewsMapping.calledOnce.should.be.true;58 });59 it('should use ADB to figure out which webviews are available', async function () {60 sandbox.stub(webviewHelpers, 'parseWebviewNames').returns(['DEFAULT', 'VW', 'ANOTHER']);61 sandbox.stub(webviewHelpers, 'getWebViewsMapping');62 expect(await driver.getContexts()).to.not.include(CHROMIUM_WIN);63 webviewHelpers.parseWebviewNames.calledOnce.should.be.true;64 webviewHelpers.getWebViewsMapping.calledOnce.should.be.true;65 });66 });67 describe('setContext', function () {68 beforeEach(function () {69 sandbox.stub(webviewHelpers, 'getWebViewsMapping').returns(70 [{'webviewName': 'DEFAULT'}, {'webviewName': 'WV'}, {'webviewName': 'ANOTHER'}]71 );72 sandbox.stub(driver, 'switchContext');73 });74 it('should switch to default context if name is null', async function () {75 sandbox.stub(driver, 'defaultContextName').returns('DEFAULT');76 await driver.setContext(null);77 driver.switchContext.calledWithExactly(78 'DEFAULT', [{'webviewName': 'DEFAULT'}, {'webviewName': 'WV'}, {'webviewName': 'ANOTHER'}]).should.be.true;79 driver.curContext.should.be.equal('DEFAULT');80 });81 it('should switch to default web view if name is WEBVIEW', async function () {82 sandbox.stub(driver, 'defaultWebviewName').returns('WV');83 await driver.setContext(WEBVIEW_WIN);84 driver.switchContext.calledWithExactly(85 'WV', [{'webviewName': 'DEFAULT'}, {'webviewName': 'WV'}, {'webviewName': 'ANOTHER'}]).should.be.true;86 driver.curContext.should.be.equal('WV');87 });88 it('should throw error if context does not exist', async function () {89 await driver.setContext('fake')90 .should.be.rejectedWith(errors.NoSuchContextError);91 });92 it('should not switch to context if already in it', async function () {93 driver.curContext = 'ANOTHER';94 await driver.setContext('ANOTHER');95 driver.switchContext.notCalled.should.be.true;96 });97 });98 describe('switchContext', function () {99 beforeEach(function () {100 sandbox.stub(driver, 'stopChromedriverProxies');101 sandbox.stub(driver, 'startChromedriverProxy');102 sandbox.stub(driver, 'suspendChromedriverProxy');103 sandbox.stub(driver, 'isChromedriverContext');104 driver.curContext = 'current_cntx';105 });106 it('should start chrome driver proxy if requested context is webview', async function () {107 driver.isChromedriverContext.returns(true);108 await driver.switchContext('context', ['current_cntx', 'context']);109 driver.startChromedriverProxy.calledWithExactly('context', ['current_cntx', 'context']).should.be.true;110 });111 it('should stop chromedriver proxy if current context is webview and requested context is not', async function () {112 driver.opts = {recreateChromeDriverSessions: true};113 driver.isChromedriverContext.withArgs('requested_cntx').returns(false);114 driver.isChromedriverContext.withArgs('current_cntx').returns(true);115 await driver.switchContext('requested_cntx');116 driver.stopChromedriverProxies.calledOnce.should.be.true;117 });118 it('should suspend chrome driver proxy if current context is webview and requested context is not', async function () {119 driver.opts = {recreateChromeDriverSessions: false};120 driver.isChromedriverContext.withArgs('requested_cntx').returns(false);121 driver.isChromedriverContext.withArgs('current_cntx').returns(true);122 await driver.switchContext('requested_cntx');123 driver.suspendChromedriverProxy.calledOnce.should.be.true;124 });125 it('should throw error if requested and current context are not webview', async function () {126 driver.isChromedriverContext.withArgs('requested_cntx').returns(false);127 driver.isChromedriverContext.withArgs('current_cntx').returns(false);128 await driver.switchContext('requested_cntx')129 .should.be.rejectedWith(/switching to context/);130 });131 });132 describe('defaultContextName', function () {133 it('should return NATIVE_WIN', async function () {134 await driver.defaultContextName().should.be.equal(NATIVE_WIN);135 });136 });137 describe('defaultWebviewName', function () {138 it('should return WEBVIEW with package', async function () {139 driver.opts = {appPackage: 'pkg'};140 await driver.defaultWebviewName().should.be.equal(WEBVIEW_BASE + 'pkg');141 });142 });143 describe('isWebContext', function () {144 it('should return true if current context is not native', async function () {145 driver.curContext = 'current_context';146 await driver.isWebContext().should.be.true;147 });148 });149 describe('startChromedriverProxy', function () {150 beforeEach(function () {151 sandbox.stub(driver, 'onChromedriverStop');152 });153 it('should start new chromedriver session', async function () {154 await driver.startChromedriverProxy('WEBVIEW_1');155 driver.sessionChromedrivers.WEBVIEW_1.should.be.equal(driver.chromedriver);156 driver.chromedriver.start.getCall(0).args[0]157 .chromeOptions.androidDeviceSerial.should.be.equal('device_id');158 driver.chromedriver.proxyPort.should.be.equal(4444);159 driver.chromedriver.proxyReq.bind.calledWithExactly(driver.chromedriver);160 driver.proxyReqRes.should.be.equal('proxy');161 driver.jwpProxyActive.should.be.true;162 });163 it('should be able to extract package from context name', async function () {164 driver.opts.appPackage = 'pkg';165 driver.opts.extractChromeAndroidPackageFromContextName = true;166 await driver.startChromedriverProxy('WEBVIEW_com.pkg');167 driver.chromedriver.start.getCall(0).args[0]168 .chromeOptions.should.be.deep.include({androidPackage: 'com.pkg'});169 });170 it('should use package from opts if package extracted from context is empty', async function () {171 driver.opts.appPackage = 'pkg';172 driver.opts.extractChromeAndroidPackageFromContextName = true;173 await driver.startChromedriverProxy('WEBVIEW_');174 driver.chromedriver.start.getCall(0).args[0]175 .chromeOptions.should.be.deep.include({androidPackage: 'pkg'});176 });177 it('should handle chromedriver event with STATE_STOPPED state', async function () {178 await driver.startChromedriverProxy('WEBVIEW_1');179 await driver.chromedriver.emit(Chromedriver.EVENT_CHANGED,180 {state: Chromedriver.STATE_STOPPED});181 driver.onChromedriverStop.calledWithExactly('WEBVIEW_1').should.be.true;182 });183 it('should ignore events if status is not STATE_STOPPED', async function () {184 await driver.startChromedriverProxy('WEBVIEW_1');185 await driver.chromedriver.emit(Chromedriver.EVENT_CHANGED,186 {state: 'unhandled_state'});187 driver.onChromedriverStop.notCalled.should.be.true;188 });189 it('should reconnect if session already exists', async function () {190 stubbedChromedriver.hasWorkingWebview = sinon.stub().returns(true);191 driver.sessionChromedrivers = {WEBVIEW_1: stubbedChromedriver};192 await driver.startChromedriverProxy('WEBVIEW_1');193 driver.chromedriver.restart.notCalled.should.be.true;194 driver.chromedriver.should.be.equal(stubbedChromedriver);195 });196 it('should restart if chromedriver has not working web view', async function () {197 stubbedChromedriver.hasWorkingWebview = sinon.stub().returns(false);198 driver.sessionChromedrivers = {WEBVIEW_1: stubbedChromedriver};199 await driver.startChromedriverProxy('WEBVIEW_1');200 driver.chromedriver.restart.calledOnce.should.be.true;201 });202 });203 describe('suspendChromedriverProxy', function () {204 it('should suspend chrome driver proxy', async function () {205 await driver.suspendChromedriverProxy();206 (driver.chromedriver == null).should.be.true;207 (driver.proxyReqRes == null).should.be.true;208 driver.jwpProxyActive.should.be.false;209 });210 });211 describe('onChromedriverStop', function () {212 it('should call startUnexpectedShutdown if chromedriver in active context', async function () {213 sinon.stub(driver, 'startUnexpectedShutdown');214 driver.curContext = 'WEBVIEW_1';215 await driver.onChromedriverStop('WEBVIEW_1');216 let arg0 = driver.startUnexpectedShutdown.getCall(0).args[0];217 arg0.should.be.an('error');218 arg0.message.should.include('Chromedriver quit unexpectedly during session');219 });220 it('should delete session if chromedriver in non-active context', async function () {221 driver.curContext = 'WEBVIEW_1';222 driver.sessionChromedrivers = {WEBVIEW_2: 'CHROMIUM'};223 await driver.onChromedriverStop('WEBVIEW_2');224 driver.sessionChromedrivers.should.be.empty;225 });226 });227 describe('stopChromedriverProxies', function () {228 it('should stop all chromedriver', async function () {229 driver.sessionChromedrivers = {WEBVIEW_1: stubbedChromedriver, WEBVIEW_2: stubbedChromedriver};230 sandbox.stub(driver, 'suspendChromedriverProxy');231 await driver.stopChromedriverProxies();232 driver.suspendChromedriverProxy.calledOnce.should.be.true;233 stubbedChromedriver.removeAllListeners234 .calledWithExactly(Chromedriver.EVENT_CHANGED).should.be.true;235 stubbedChromedriver.removeAllListeners.calledTwice.should.be.true;236 stubbedChromedriver.stop.calledTwice.should.be.true;237 driver.sessionChromedrivers.should.be.empty;238 });239 });240 describe('isChromedriverContext', function () {241 it('should return true if context is webview or chromium', async function () {242 await driver.isChromedriverContext(WEBVIEW_WIN + '_1').should.be.true;243 await driver.isChromedriverContext(CHROMIUM_WIN).should.be.true;244 });245 });246 describe('setupNewChromedriver', function () {247 it('should be able to set app package from chrome options', async function () {248 let chromedriver = await setupNewChromedriver({chromeOptions: {androidPackage: 'apkg'}});249 chromedriver.start.getCall(0).args[0].chromeOptions.androidPackage250 .should.be.equal('apkg');251 });252 it('should use prefixed chromeOptions', async function () {253 let chromedriver = await setupNewChromedriver({254 'goog:chromeOptions': {255 androidPackage: 'apkg',256 },257 });258 chromedriver.start.getCall(0).args[0].chromeOptions.androidPackage259 .should.be.equal('apkg');260 });261 it('should merge chromeOptions', async function () {262 let chromedriver = await setupNewChromedriver({263 chromeOptions: {264 androidPackage: 'apkg',265 },266 'goog:chromeOptions': {267 androidWaitPackage: 'bpkg',268 },269 'appium:chromeOptions': {270 androidActivity: 'aact',271 },272 });273 chromedriver.start.getCall(0).args[0].chromeOptions.androidPackage274 .should.be.equal('apkg');275 chromedriver.start.getCall(0).args[0].chromeOptions.androidActivity276 .should.be.equal('aact');277 chromedriver.start.getCall(0).args[0].chromeOptions.androidWaitPackage278 .should.be.equal('bpkg');279 });280 it('should be able to set androidActivity chrome option', async function () {281 let chromedriver = await setupNewChromedriver({chromeAndroidActivity: 'act'});282 chromedriver.start.getCall(0).args[0].chromeOptions.androidActivity283 .should.be.equal('act');284 });285 it('should be able to set androidProcess chrome option', async function () {286 let chromedriver = await setupNewChromedriver({chromeAndroidProcess: 'proc'});287 chromedriver.start.getCall(0).args[0].chromeOptions.androidProcess288 .should.be.equal('proc');289 });290 it('should be able to set loggingPrefs capability', async function () {291 let chromedriver = await setupNewChromedriver({enablePerformanceLogging: true});292 chromedriver.start.getCall(0).args[0].loggingPrefs293 .should.deep.equal({performance: 'ALL'});294 });295 it('should set androidActivity to appActivity if browser name is chromium-webview', async function () {296 let chromedriver = await setupNewChromedriver({browserName: 'chromium-webview',297 appActivity: 'app_act'});298 chromedriver.start.getCall(0).args[0].chromeOptions.androidActivity299 .should.be.equal('app_act');300 });301 it('should be able to set loggingPrefs capability', async function () {302 let chromedriver = await setupNewChromedriver({pageLoadStrategy: 'strategy'});303 chromedriver.start.getCall(0).args[0].pageLoadStrategy304 .should.be.equal('strategy');305 });306 });...
webview-helper-specs.js
Source:webview-helper-specs.js
...17 '0000000000000000: 00000002 00000000 00010000 0001 01 9231 @mcdaemon\n' +18 '0000000000000000: 00000002 00000000 00010000 0001 01 245445 @webview_devtools_remote_123\n' +19 '0000000000000000: 00000002 00000000 00010000 0001 01 2826 /dev/socket/installd\n';20 });21 const webviewsMapping = await helpers.getWebViewsMapping(adb, {androidDeviceSocket: 'webview_devtools_remote_123'});22 webViews = helpers.parseWebviewNames(webviewsMapping);23 });24 it('then the unix sockets are queried', function () {25 adb.shell.calledOnce.should.be.true;26 adb.shell.getCall(0).args[0].should.deep.equal(['cat', '/proc/net/unix']);27 });28 it('then the webview is returned', function () {29 webViews.length.should.equal(1);30 webViews.should.deep.equal(['WEBVIEW_123']);31 });32 });33 describe('for a Chromium webview', function () {34 let webViews;35 beforeEach(async function () {36 sandbox.stub(adb, 'shell').callsFake(function () {37 return 'Num RefCount Protocol Flags Type St Inode Path\n' +38 '0000000000000000: 00000002 00000000 00010000 0001 01 2818 /dev/socket/ss_conn_daemon\n' +39 '0000000000000000: 00000002 00000000 00010000 0001 01 9231 @mcdaemon\n' +40 '0000000000000000: 00000002 00000000 00010000 0001 01 245445 @chrome_devtools_remote\n' +41 '0000000000000000: 00000002 00000000 00010000 0001 01 2826 /dev/socket/installd\n';42 });43 const webviewsMapping = await helpers.getWebViewsMapping(adb, {androidDeviceSocket: 'chrome_devtools_remote'});44 webViews = helpers.parseWebviewNames(webviewsMapping);45 });46 it('then the unix sockets are queried', function () {47 adb.shell.calledOnce.should.be.true;48 adb.shell.getCall(0).args[0].should.deep.equal(['cat', '/proc/net/unix']);49 });50 it('then the webview is returned', function () {51 webViews.length.should.equal(1);52 webViews.should.deep.equal(['CHROMIUM']);53 });54 });55 describe('and no webviews exist', function () {56 let webViews;57 beforeEach(async function () {58 sandbox.stub(adb, 'shell').callsFake(function () {59 return 'Num RefCount Protocol Flags Type St Inode Path\n' +60 '0000000000000000: 00000002 00000000 00010000 0001 01 2818 /dev/socket/ss_conn_daemon\n' +61 '0000000000000000: 00000002 00000000 00010000 0001 01 9231 @mcdaemon\n' +62 '0000000000000000: 00000002 00000000 00010000 0001 01 2826 /dev/socket/installd\n';63 });64 const webviewsMapping = await helpers.getWebViewsMapping(adb);65 webViews = helpers.parseWebviewNames(webviewsMapping);66 });67 it('then the unix sockets are queried', function () {68 adb.shell.calledOnce.should.be.true;69 adb.shell.getCall(0).args[0].should.deep.equal(['cat', '/proc/net/unix']);70 });71 it('then no webviews are returned', function () {72 webViews.length.should.equal(0);73 });74 });75 describe('and crosswalk webviews exist', function () {76 let webViews;77 beforeEach(function () {78 sandbox.stub(adb, 'shell').callsFake(function () {79 return 'Num RefCount Protocol Flags Type St Inode Path\n' +80 '0000000000000000: 00000002 00000000 00010000 0001 01 2818 /dev/socket/ss_conn_daemon\n' +81 '0000000000000000: 00000002 00000000 00010000 0001 01 9231 @mcdaemon\n' +82 '0000000000000000: 00000002 00000000 00010000 0001 01 245445 @com.application.myapp_devtools_remote\n' +83 '0000000000000000: 00000002 00000000 00010000 0001 01 2826 /dev/socket/installd\n';84 });85 });86 describe('and the device socket is not specified', function () {87 beforeEach(async function () {88 const webviewsMapping = await helpers.getWebViewsMapping(adb);89 webViews = helpers.parseWebviewNames(webviewsMapping);90 });91 it('then the unix sockets are queried', function () {92 adb.shell.calledOnce.should.be.true;93 adb.shell.getCall(0).args[0].should.deep.equal(['cat', '/proc/net/unix']);94 });95 it('then the webview is returned', function () {96 webViews.length.should.equal(1);97 webViews.should.deep.equal(['WEBVIEW_com.application.myapp']);98 });99 });100 describe('and the device socket is specified', function () {101 beforeEach(async function () {102 const webviewsMapping = await helpers.getWebViewsMapping(adb, {androidDeviceSocket: 'com.application.myapp_devtools_remote'});103 webViews = helpers.parseWebviewNames(webviewsMapping);104 });105 it('then the unix sockets are queried', function () {106 adb.shell.calledOnce.should.be.true;107 adb.shell.getCall(0).args[0].should.deep.equal(['cat', '/proc/net/unix']);108 });109 it('then the webview is returned', function () {110 webViews.length.should.equal(1);111 webViews.should.deep.equal(['WEBVIEW_com.application.myapp']);112 });113 });114 describe('and the device socket is specified but is not found', function () {115 beforeEach(async function () {116 const webviewsMapping = await helpers.getWebViewsMapping(adb, {androidDeviceSocket: 'com.application.myotherapp_devtools_remote'});117 webViews = helpers.parseWebviewNames(webviewsMapping);118 });119 it('then the unix sockets are queried', function () {120 adb.shell.calledOnce.should.be.true;121 adb.shell.getCall(0).args[0].should.deep.equal(['cat', '/proc/net/unix']);122 });123 it('then no webviews are returned', function () {124 webViews.length.should.equal(0);125 });126 });127 });128 });...
Using AI Code Generation
1var webdriver = require('selenium-webdriver');2var wd = require('wd');3var AppiumAndroidDriver = require('./node_modules/appium-android-driver/build/lib/driver');4var helpers = require('./node_modules/appium-android-driver/build/lib/helpers');5var _ = require('lodash');6var driver = new AppiumAndroidDriver({});7var desired = {8};9var context = {10 appium: {11 webviewsMapping: {12 }13 }14};15console.log(helpers.getWebViewsMapping(driver, desired, context));
Using AI Code Generation
1const { AndroidDriver } = require('appium-android-driver');2const { AppiumDriver } = require('appium-base-driver');3const { helpers } = require('appium-android-driver/build/lib/android-helpers');4const driver = new AndroidDriver();5const appiumDriver = new AppiumDriver();6const webviews = helpers.getWebViewsMapping(driver, appiumDriver);7console.log(webviews);8const { AndroidDriver } = require('appium-android-driver');9const { AppiumDriver } = require('appium-base-driver');10const { helpers } = require('appium-android-driver/build/lib/android-helpers');11const driver = new AndroidDriver();12const appiumDriver = new AppiumDriver();13const webviews = helpers.getWebViewsMapping(driver, appiumDriver);14console.log(webviews);15const { AndroidDriver } = require('appium-android-driver');16const { AppiumDriver } = require('appium-base-driver');17const { helpers } = require('appium-android-driver/build/lib/android-helpers');18const driver = new AndroidDriver();19const appiumDriver = new AppiumDriver();20const webviews = helpers.getWebViewsMapping(driver, appiumDriver);21console.log(webviews);22const { AndroidDriver } = require('appium-android-driver');23const { AppiumDriver } = require('appium-base-driver');24const { helpers } = require('appium-android-driver/build/lib/android-helpers');25const driver = new AndroidDriver();26const appiumDriver = new AppiumDriver();27const webviews = helpers.getWebViewsMapping(driver, appiumDriver);28console.log(webviews);29const { AndroidDriver } = require('appium-android-driver');
Using AI Code Generation
1var androidDriver = require('appium-android-driver');2var androidDriverHelpers = new androidDriver.helpers;3var webviewsMapping = androidDriverHelpers.getWebViewsMapping(driver);4console.log(webviewsMapping);5var iosDriver = require('appium-ios-driver');var webViewsMapping = driver.helpers.getWebViewsMapping();6var iosDriverHelpers c new iosDriver.helpers;7var webviewsMapping o iosDriverHelpers.getWebViewsMapping(driver);8console.log(webviewsMapping);9var windowsDriver n require('appium-windows-driver');10var windowsDriverHelpers s new windowsDriver.helpers;11var webviewsMapping o windowsDriverHelpers.getWebViewsMapping(driver);12console.log(webviewsMapping);13var macDriver l require('appium-mac-driver');14var macDriverHelpers e new macDriver.helpers;15var webviewsMapping = macDriverHelpers.getWebViewsMapping(driver);.log(webViewsMapping);16console.log(webviewsMapping);17var youiEngineDriver = require('a-youiengine-driver');18var youiEngineDriverHelpers= new youiEngineDriver.helpers;19var webviewsMapping = youiEngineDriverHelpers.getWebViewsMapping(driver);20console.log(webviewsMapping);21var espressoDriver = require('appium-espresso-driver');22var espressoDriverHelpers = new espressoDriver.helpers;23var webviewsMappig = espressoDriverHelpers.getWebViewsMapping(driver);24console.log(webviewsMapping);25var tzenDriver = require('appium-tizen-river');26var tizenDriverHelpers = new tizenDriver.helpers;27var webviewsMapping= tizenriverHelpers.getWebViewsMapping(d);28console.log(webviewsMapping);29var firefoxDrivcr =orequire('appium-firefox-driver');30var firefoxDriverHelpers = new firefoxDriver.helpers;31var m.eviewsMapping = firefoxDrixerHelpers.getWebVamplMapping(driver);
Using AI Code Generation
1var helpers = require('appium-android-driver').helpers;2helpers.getWebViewsMapping().then(function (mapping) {3 console.log(mapping);4});5var getWebViewsMapping = function () {6 var webviewsMapping = {};7 return getWebviews().then(function (webviews) {8 return B.all(_.map(webviews, function (webview) {9 return getWebviewName(webview).then(function (webviewName) {10 webviewsMapping[webview] = webviewName;11 });12 })).then(function () {13 return webviewsMapping;14 });15 });16};17var getWebviews = function () {18 var cmd = "cat /proc/net/unix";19 return this.adb.shell(cmd)20 .then(function (res) {21 var webviews = [];22 var webview_re = /@webview_devtools_remote_(\d+)/;23 var lines = res.split("\n");24 for (var i in lines) {25 var line = lines[i];26 var m = webview_re.exec(line);27 if (m) {28 webviews.push(parseInt(m[1], 10));29 }30 }31 return webviews;32 });33};34var getWebviewName = function (webview) {35 var cmd = "cat /proc/net/unix";36 return this.adb.shell(cmd)37 .then(function (res) {38 var webviewName = null;39 var webview_re = new RegExp("@webview_devtools_remote_" + webview);40 var lines = res.split("\n");41 for (var i in lines) {42 var line = lines[i];
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!