1 | import _ from 'lodash';
|
2 | import log from './logger';
|
3 | import { getBuildInfo, updateBuildInfo, APPIUM_VER } from './config';
|
4 | import { BaseDriver, errors, isSessionCommand } from 'appium-base-driver';
|
5 | import B from 'bluebird';
|
6 | import AsyncLock from 'async-lock';
|
7 | import { parseCapsForInnerDriver, getPackageVersion, pullSettings } from './utils';
|
8 | import semver from 'semver';
|
9 | import wrap from 'word-wrap';
|
10 | import { EOL } from 'os';
|
11 | import { util } from 'appium-support';
|
12 |
|
13 |
|
14 | const PLATFORMS = {
|
15 | FAKE: 'fake',
|
16 | ANDROID: 'android',
|
17 | IOS: 'ios',
|
18 | APPLE_TVOS: 'tvos',
|
19 | WINDOWS: 'windows',
|
20 | MAC: 'mac',
|
21 | TIZEN: 'tizen',
|
22 | LINUX: 'linux',
|
23 | ROKU: 'roku',
|
24 | WEBOS: 'webos'
|
25 | };
|
26 |
|
27 | const AUTOMATION_NAMES = {
|
28 | APPIUM: 'Appium',
|
29 | UIAUTOMATOR2: 'UiAutomator2',
|
30 | UIAUTOMATOR1: 'UiAutomator1',
|
31 | XCUITEST: 'XCUITest',
|
32 | YOUIENGINE: 'YouiEngine',
|
33 | ESPRESSO: 'Espresso',
|
34 | TIZEN: 'Tizen',
|
35 | FAKE: 'Fake',
|
36 | INSTRUMENTS: 'Instruments',
|
37 | WINDOWS: 'Windows',
|
38 | MAC: 'Mac',
|
39 | MAC2: 'Mac2',
|
40 | FLUTTER: 'Flutter',
|
41 | SAFARI: 'Safari',
|
42 | GECKO: 'Gecko',
|
43 | ROKU: 'Roku',
|
44 | WEBOS: 'WebOS'
|
45 | };
|
46 | const DRIVER_MAP = {
|
47 | [AUTOMATION_NAMES.UIAUTOMATOR2.toLowerCase()]: {
|
48 | driverClassName: 'AndroidUiautomator2Driver',
|
49 | driverPackage: 'appium-uiautomator2-driver',
|
50 | },
|
51 | [AUTOMATION_NAMES.XCUITEST.toLowerCase()]: {
|
52 | driverClassName: 'XCUITestDriver',
|
53 | driverPackage: 'appium-xcuitest-driver',
|
54 | },
|
55 | [AUTOMATION_NAMES.YOUIENGINE.toLowerCase()]: {
|
56 | driverClassName: 'YouiEngineDriver',
|
57 | driverPackage: 'appium-youiengine-driver',
|
58 | },
|
59 | [AUTOMATION_NAMES.FAKE.toLowerCase()]: {
|
60 | driverClassName: 'FakeDriver',
|
61 | driverPackage: 'appium-fake-driver',
|
62 | },
|
63 | [AUTOMATION_NAMES.UIAUTOMATOR1.toLowerCase()]: {
|
64 | driverClassName: 'AndroidDriver',
|
65 | driverPackage: 'appium-android-driver',
|
66 | },
|
67 | [AUTOMATION_NAMES.INSTRUMENTS.toLowerCase()]: {
|
68 | driverClassName: 'IosDriver',
|
69 | driverPackage: 'appium-ios-driver',
|
70 | },
|
71 | [AUTOMATION_NAMES.WINDOWS.toLowerCase()]: {
|
72 | driverClassName: 'WindowsDriver',
|
73 | driverPackage: 'appium-windows-driver',
|
74 | },
|
75 | [AUTOMATION_NAMES.MAC.toLowerCase()]: {
|
76 | driverClassName: 'MacDriver',
|
77 | driverPackage: 'appium-mac-driver',
|
78 | },
|
79 | [AUTOMATION_NAMES.MAC2.toLowerCase()]: {
|
80 | driverClassName: 'Mac2Driver',
|
81 | driverPackage: 'appium-mac2-driver',
|
82 | },
|
83 | [AUTOMATION_NAMES.ESPRESSO.toLowerCase()]: {
|
84 | driverClassName: 'EspressoDriver',
|
85 | driverPackage: 'appium-espresso-driver',
|
86 | },
|
87 | [AUTOMATION_NAMES.TIZEN.toLowerCase()]: {
|
88 | driverClassName: 'TizenDriver',
|
89 | driverPackage: 'appium-tizen-driver',
|
90 | },
|
91 | [AUTOMATION_NAMES.FLUTTER.toLowerCase()]: {
|
92 | driverClassName: 'FlutterDriver',
|
93 | driverPackage: 'appium-flutter-driver'
|
94 | },
|
95 | [AUTOMATION_NAMES.SAFARI.toLowerCase()]: {
|
96 | driverClassName: 'SafariDriver',
|
97 | driverPackage: 'appium-safari-driver'
|
98 | },
|
99 | [AUTOMATION_NAMES.GECKO.toLowerCase()]: {
|
100 | driverClassName: 'GeckoDriver',
|
101 | driverPackage: 'appium-geckodriver'
|
102 | },
|
103 | [AUTOMATION_NAMES.ROKU.toLowerCase()]: {
|
104 | driverClassName: 'RokuDriver',
|
105 | driverPackage: 'appium-roku-driver'
|
106 | },
|
107 | [AUTOMATION_NAMES.WEBOS.toLowerCase()]: {
|
108 | driverClassName: 'WebOSDriver',
|
109 | driverPackage: 'appium-webos-driver'
|
110 | },
|
111 | };
|
112 |
|
113 | const PLATFORMS_MAP = {
|
114 | [PLATFORMS.FAKE]: () => AUTOMATION_NAMES.FAKE,
|
115 | [PLATFORMS.ANDROID]: () => {
|
116 |
|
117 |
|
118 | const logDividerLength = 70;
|
119 |
|
120 | const automationWarning = [
|
121 | `The 'automationName' capability was not provided in the desired capabilities for this Android session`,
|
122 | `Setting 'automationName=UiAutomator2' by default and using the UiAutomator2 Driver`,
|
123 | `The next major version of Appium (2.x) will **require** the 'automationName' capability to be set for all sessions on all platforms`,
|
124 | `In previous versions (Appium <= 1.13.x), the default was 'automationName=UiAutomator1'`,
|
125 | `If you wish to use that automation instead of UiAutomator2, please add 'automationName=UiAutomator1' to your desired capabilities`,
|
126 | `For more information about drivers, please visit http://appium.io/docs/en/about-appium/intro/ and explore the 'Drivers' menu`
|
127 | ];
|
128 |
|
129 | let divider = `${EOL}${_.repeat('=', logDividerLength)}${EOL}`;
|
130 | let automationWarningString = divider;
|
131 | automationWarningString += ` DEPRECATION WARNING:` + EOL;
|
132 | for (let log of automationWarning) {
|
133 | automationWarningString += EOL + wrap(log, {width: logDividerLength - 2}) + EOL;
|
134 | }
|
135 | automationWarningString += divider;
|
136 |
|
137 |
|
138 | log.warn(automationWarningString);
|
139 |
|
140 | return AUTOMATION_NAMES.UIAUTOMATOR2;
|
141 | },
|
142 | [PLATFORMS.IOS]: (caps) => {
|
143 | const platformVersion = semver.valid(semver.coerce(caps.platformVersion));
|
144 | log.warn(`DeprecationWarning: 'automationName' capability was not provided. ` +
|
145 | `Future versions of Appium will require 'automationName' capability to be set for iOS sessions.`);
|
146 | if (platformVersion && semver.satisfies(platformVersion, '>=10.0.0')) {
|
147 | log.info('Requested iOS support with version >= 10, ' +
|
148 | `using '${AUTOMATION_NAMES.XCUITEST}' ` +
|
149 | 'driver instead of UIAutomation-based driver, since the ' +
|
150 | 'latter is unsupported on iOS 10 and up.');
|
151 | return AUTOMATION_NAMES.XCUITEST;
|
152 | }
|
153 |
|
154 | return AUTOMATION_NAMES.INSTRUMENTS;
|
155 | },
|
156 | [PLATFORMS.APPLE_TVOS]: () => AUTOMATION_NAMES.XCUITEST,
|
157 | [PLATFORMS.WINDOWS]: () => AUTOMATION_NAMES.WINDOWS,
|
158 | [PLATFORMS.MAC]: () => AUTOMATION_NAMES.MAC,
|
159 | [PLATFORMS.TIZEN]: () => AUTOMATION_NAMES.TIZEN,
|
160 | [PLATFORMS.LINUX]: () => AUTOMATION_NAMES.GECKO,
|
161 | [PLATFORMS.ROKU]: () => AUTOMATION_NAMES.ROKU,
|
162 | [PLATFORMS.WEBOS]: () => AUTOMATION_NAMES.WEBOS
|
163 | };
|
164 |
|
165 | const desiredCapabilityConstraints = {
|
166 | automationName: {
|
167 | presence: false,
|
168 | isString: true,
|
169 | inclusionCaseInsensitive: _.values(AUTOMATION_NAMES),
|
170 | },
|
171 | platformName: {
|
172 | presence: true,
|
173 | isString: true,
|
174 | inclusionCaseInsensitive: _.keys(PLATFORMS_MAP),
|
175 | },
|
176 | };
|
177 |
|
178 | const sessionsListGuard = new AsyncLock();
|
179 | const pendingDriversGuard = new AsyncLock();
|
180 |
|
181 | class AppiumDriver extends BaseDriver {
|
182 | constructor (args) {
|
183 |
|
184 |
|
185 |
|
186 |
|
187 | if (args.tmpDir) {
|
188 | process.env.APPIUM_TMP_DIR = args.tmpDir;
|
189 | }
|
190 |
|
191 | super(args);
|
192 |
|
193 | this.desiredCapConstraints = desiredCapabilityConstraints;
|
194 |
|
195 |
|
196 | this.newCommandTimeoutMs = 0;
|
197 |
|
198 | this.args = Object.assign({}, args);
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | this.sessions = {};
|
204 |
|
205 |
|
206 |
|
207 |
|
208 | this.pendingDrivers = {};
|
209 |
|
210 |
|
211 | updateBuildInfo();
|
212 | }
|
213 |
|
214 | |
215 |
|
216 |
|
217 | get isCommandsQueueEnabled () {
|
218 | return false;
|
219 | }
|
220 |
|
221 | sessionExists (sessionId) {
|
222 | const dstSession = this.sessions[sessionId];
|
223 | return dstSession && dstSession.sessionId !== null;
|
224 | }
|
225 |
|
226 | driverForSession (sessionId) {
|
227 | return this.sessions[sessionId];
|
228 | }
|
229 |
|
230 | getDriverAndVersionForCaps (caps) {
|
231 | if (!_.isString(caps.platformName)) {
|
232 | throw new Error('You must include a platformName capability');
|
233 | }
|
234 |
|
235 | const platformName = caps.platformName.toLowerCase();
|
236 |
|
237 |
|
238 | let automationNameCap = caps.automationName;
|
239 | if (!_.isString(automationNameCap) || automationNameCap.toLowerCase() === 'appium') {
|
240 | const driverSelector = PLATFORMS_MAP[platformName];
|
241 | if (driverSelector) {
|
242 | automationNameCap = driverSelector(caps);
|
243 | }
|
244 | }
|
245 | automationNameCap = _.toLower(automationNameCap);
|
246 |
|
247 | let failureVerb = 'find';
|
248 | let suggestion = 'Please check your desired capabilities';
|
249 | if (_.isPlainObject(DRIVER_MAP[automationNameCap])) {
|
250 | try {
|
251 | const {driverPackage, driverClassName} = DRIVER_MAP[automationNameCap];
|
252 | const driver = require(driverPackage)[driverClassName];
|
253 | return {
|
254 | driver,
|
255 | version: this.getDriverVersion(driver.name, driverPackage),
|
256 | };
|
257 | } catch (e) {
|
258 | log.debug(e);
|
259 | failureVerb = 'load';
|
260 | suggestion = 'Please verify your Appium installation';
|
261 | }
|
262 | }
|
263 |
|
264 | const msg = _.isString(caps.automationName)
|
265 | ? `Could not ${failureVerb} a driver for automationName '${caps.automationName}' and platformName ` +
|
266 | `'${caps.platformName}'`
|
267 | : `Could not ${failureVerb} a driver for platformName '${caps.platformName}'`;
|
268 | throw new Error(`${msg}. ${suggestion}`);
|
269 | }
|
270 |
|
271 | getDriverVersion (driverName, driverPackage) {
|
272 | const version = getPackageVersion(driverPackage);
|
273 | if (version) {
|
274 | return version;
|
275 | }
|
276 | log.warn(`Unable to get version of driver '${driverName}'`);
|
277 | }
|
278 |
|
279 | async getStatus () {
|
280 | return {
|
281 | build: _.clone(getBuildInfo()),
|
282 | };
|
283 | }
|
284 |
|
285 | async getSessions () {
|
286 | const sessions = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions);
|
287 | return _.toPairs(sessions)
|
288 | .map(([id, driver]) => ({id, capabilities: driver.caps}));
|
289 | }
|
290 |
|
291 | printNewSessionAnnouncement (driverName, driverVersion) {
|
292 | const introString = driverVersion
|
293 | ? `Appium v${APPIUM_VER} creating new ${driverName} (v${driverVersion}) session`
|
294 | : `Appium v${APPIUM_VER} creating new ${driverName} session`;
|
295 | log.info(introString);
|
296 | }
|
297 |
|
298 | |
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 | async createSession (jsonwpCaps, reqCaps, w3cCapabilities) {
|
306 | const defaultCapabilities = _.cloneDeep(this.args.defaultCapabilities);
|
307 | const defaultSettings = pullSettings(defaultCapabilities);
|
308 | jsonwpCaps = _.cloneDeep(jsonwpCaps);
|
309 | const jwpSettings = Object.assign({}, defaultSettings, pullSettings(jsonwpCaps));
|
310 | w3cCapabilities = _.cloneDeep(w3cCapabilities);
|
311 |
|
312 |
|
313 |
|
314 |
|
315 | const w3cSettings = Object.assign({}, jwpSettings);
|
316 | Object.assign(w3cSettings, pullSettings((w3cCapabilities || {}).alwaysMatch || {}));
|
317 | for (const firstMatchEntry of ((w3cCapabilities || {}).firstMatch || [])) {
|
318 | Object.assign(w3cSettings, pullSettings(firstMatchEntry));
|
319 | }
|
320 |
|
321 | let protocol;
|
322 | let innerSessionId, dCaps;
|
323 | try {
|
324 |
|
325 | const parsedCaps = parseCapsForInnerDriver(
|
326 | jsonwpCaps,
|
327 | w3cCapabilities,
|
328 | this.desiredCapConstraints,
|
329 | defaultCapabilities
|
330 | );
|
331 |
|
332 | const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, error} = parsedCaps;
|
333 | protocol = parsedCaps.protocol;
|
334 |
|
335 |
|
336 | if (error) {
|
337 | throw error;
|
338 | }
|
339 |
|
340 | const {driver: InnerDriver, version: driverVersion} = this.getDriverAndVersionForCaps(desiredCaps);
|
341 | this.printNewSessionAnnouncement(InnerDriver.name, driverVersion);
|
342 |
|
343 | if (this.args.sessionOverride) {
|
344 | await this.deleteAllSessions();
|
345 | }
|
346 |
|
347 | let runningDriversData, otherPendingDriversData;
|
348 | const d = new InnerDriver(this.args);
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 | if (this.args.relaxedSecurityEnabled) {
|
355 | log.info(`Applying relaxed security to '${InnerDriver.name}' as per ` +
|
356 | `server command line argument. All insecure features will be ` +
|
357 | `enabled unless explicitly disabled by --deny-insecure`);
|
358 | d.relaxedSecurityEnabled = true;
|
359 | }
|
360 |
|
361 | if (!_.isEmpty(this.args.denyInsecure)) {
|
362 | log.info('Explicitly preventing use of insecure features:');
|
363 | this.args.denyInsecure.map((a) => log.info(` ${a}`));
|
364 | d.denyInsecure = this.args.denyInsecure;
|
365 | }
|
366 |
|
367 | if (!_.isEmpty(this.args.allowInsecure)) {
|
368 | log.info('Explicitly enabling use of insecure features:');
|
369 | this.args.allowInsecure.map((a) => log.info(` ${a}`));
|
370 | d.allowInsecure = this.args.allowInsecure;
|
371 | }
|
372 |
|
373 |
|
374 | d.server = this.server;
|
375 | try {
|
376 | runningDriversData = await this.curSessionDataForDriver(InnerDriver);
|
377 | } catch (e) {
|
378 | throw new errors.SessionNotCreatedError(e.message);
|
379 | }
|
380 | await pendingDriversGuard.acquire(AppiumDriver.name, () => {
|
381 | this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
|
382 | otherPendingDriversData = this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData);
|
383 | this.pendingDrivers[InnerDriver.name].push(d);
|
384 | });
|
385 |
|
386 | try {
|
387 | [innerSessionId, dCaps] = await d.createSession(
|
388 | processedJsonwpCapabilities,
|
389 | reqCaps,
|
390 | processedW3CCapabilities,
|
391 | [...runningDriversData, ...otherPendingDriversData]
|
392 | );
|
393 | protocol = d.protocol;
|
394 | await sessionsListGuard.acquire(AppiumDriver.name, () => {
|
395 | this.sessions[innerSessionId] = d;
|
396 | });
|
397 | } finally {
|
398 | await pendingDriversGuard.acquire(AppiumDriver.name, () => {
|
399 | _.pull(this.pendingDrivers[InnerDriver.name], d);
|
400 | });
|
401 | }
|
402 |
|
403 | this.attachUnexpectedShutdownHandler(d, innerSessionId);
|
404 |
|
405 | log.info(`New ${InnerDriver.name} session created successfully, session ` +
|
406 | `${innerSessionId} added to master session list`);
|
407 |
|
408 |
|
409 | d.startNewCommandTimeout();
|
410 |
|
411 |
|
412 | if (d.isW3CProtocol() && !_.isEmpty(w3cSettings)) {
|
413 | log.info(`Applying the initial values to Appium settings parsed from W3C caps: ` +
|
414 | JSON.stringify(w3cSettings));
|
415 | await d.updateSettings(w3cSettings);
|
416 | } else if (d.isMjsonwpProtocol() && !_.isEmpty(jwpSettings)) {
|
417 | log.info(`Applying the initial values to Appium settings parsed from MJSONWP caps: ` +
|
418 | JSON.stringify(jwpSettings));
|
419 | await d.updateSettings(jwpSettings);
|
420 | }
|
421 | } catch (error) {
|
422 | return {
|
423 | protocol,
|
424 | error,
|
425 | };
|
426 | }
|
427 |
|
428 | return {
|
429 | protocol,
|
430 | value: [innerSessionId, dCaps, protocol]
|
431 | };
|
432 | }
|
433 |
|
434 | attachUnexpectedShutdownHandler (driver, innerSessionId) {
|
435 | const removeSessionFromMasterList = (cause = new Error('Unknown error')) => {
|
436 | log.warn(`Closing session, cause was '${cause.message}'`);
|
437 | log.info(`Removing session '${innerSessionId}' from our master session list`);
|
438 | delete this.sessions[innerSessionId];
|
439 | };
|
440 |
|
441 |
|
442 | if (_.isFunction((driver.onUnexpectedShutdown || {}).then)) {
|
443 |
|
444 |
|
445 |
|
446 | driver.onUnexpectedShutdown
|
447 |
|
448 | .then(() => {
|
449 |
|
450 | throw new Error('Unexpected shutdown');
|
451 | })
|
452 | .catch((e) => {
|
453 |
|
454 |
|
455 | if (!(e instanceof B.CancellationError)) {
|
456 | removeSessionFromMasterList(e);
|
457 | }
|
458 | });
|
459 | } else if (_.isFunction(driver.onUnexpectedShutdown)) {
|
460 |
|
461 | driver.onUnexpectedShutdown(removeSessionFromMasterList);
|
462 | } else {
|
463 | log.warn(`Failed to attach the unexpected shutdown listener. ` +
|
464 | `Is 'onUnexpectedShutdown' method available for '${driver.constructor.name}'?`);
|
465 | }
|
466 | }
|
467 |
|
468 | async curSessionDataForDriver (InnerDriver) {
|
469 | const sessions = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions);
|
470 | const data = _.values(sessions)
|
471 | .filter((s) => s.constructor.name === InnerDriver.name)
|
472 | .map((s) => s.driverData);
|
473 | for (let datum of data) {
|
474 | if (!datum) {
|
475 | throw new Error(`Problem getting session data for driver type ` +
|
476 | `${InnerDriver.name}; does it implement 'get ` +
|
477 | `driverData'?`);
|
478 | }
|
479 | }
|
480 | return data;
|
481 | }
|
482 |
|
483 | async deleteSession (sessionId) {
|
484 | let protocol;
|
485 | try {
|
486 | let otherSessionsData = null;
|
487 | let dstSession = null;
|
488 | await sessionsListGuard.acquire(AppiumDriver.name, () => {
|
489 | if (!this.sessions[sessionId]) {
|
490 | return;
|
491 | }
|
492 | const curConstructorName = this.sessions[sessionId].constructor.name;
|
493 | otherSessionsData = _.toPairs(this.sessions)
|
494 | .filter(([key, value]) => value.constructor.name === curConstructorName && key !== sessionId)
|
495 | .map(([, value]) => value.driverData);
|
496 | dstSession = this.sessions[sessionId];
|
497 | protocol = dstSession.protocol;
|
498 | log.info(`Removing session ${sessionId} from our master session list`);
|
499 |
|
500 |
|
501 |
|
502 | delete this.sessions[sessionId];
|
503 | });
|
504 | return {
|
505 | protocol,
|
506 | value: await dstSession.deleteSession(sessionId, otherSessionsData),
|
507 | };
|
508 | } catch (e) {
|
509 | log.error(`Had trouble ending session ${sessionId}: ${e.message}`);
|
510 | return {
|
511 | protocol,
|
512 | error: e,
|
513 | };
|
514 | }
|
515 | }
|
516 |
|
517 | async deleteAllSessions (opts = {}) {
|
518 | const sessionsCount = _.size(this.sessions);
|
519 | if (0 === sessionsCount) {
|
520 | log.debug('There are no active sessions for cleanup');
|
521 | return;
|
522 | }
|
523 |
|
524 | const {
|
525 | force = false,
|
526 | reason,
|
527 | } = opts;
|
528 | log.debug(`Cleaning up ${util.pluralize('active session', sessionsCount, true)}`);
|
529 | const cleanupPromises = force
|
530 | ? _.values(this.sessions).map((drv) => drv.startUnexpectedShutdown(reason && new Error(reason)))
|
531 | : _.keys(this.sessions).map((id) => this.deleteSession(id));
|
532 | for (const cleanupPromise of cleanupPromises) {
|
533 | try {
|
534 | await cleanupPromise;
|
535 | } catch (e) {
|
536 | log.debug(e);
|
537 | }
|
538 | }
|
539 | }
|
540 |
|
541 | async executeCommand (cmd, ...args) {
|
542 |
|
543 |
|
544 | if (cmd === 'getStatus') {
|
545 | return await this.getStatus();
|
546 | }
|
547 |
|
548 | if (isAppiumDriverCommand(cmd)) {
|
549 | return await super.executeCommand(cmd, ...args);
|
550 | }
|
551 |
|
552 | const sessionId = _.last(args);
|
553 | const dstSession = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions[sessionId]);
|
554 | if (!dstSession) {
|
555 | throw new Error(`The session with id '${sessionId}' does not exist`);
|
556 | }
|
557 |
|
558 | let res = {
|
559 | protocol: dstSession.protocol
|
560 | };
|
561 |
|
562 | try {
|
563 | res.value = await dstSession.executeCommand(cmd, ...args);
|
564 | } catch (e) {
|
565 | res.error = e;
|
566 | }
|
567 | return res;
|
568 | }
|
569 |
|
570 | proxyActive (sessionId) {
|
571 | const dstSession = this.sessions[sessionId];
|
572 | return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
|
573 | }
|
574 |
|
575 | getProxyAvoidList (sessionId) {
|
576 | const dstSession = this.sessions[sessionId];
|
577 | return dstSession ? dstSession.getProxyAvoidList() : [];
|
578 | }
|
579 |
|
580 | canProxy (sessionId) {
|
581 | const dstSession = this.sessions[sessionId];
|
582 | return dstSession && dstSession.canProxy(sessionId);
|
583 | }
|
584 | }
|
585 |
|
586 |
|
587 |
|
588 | function isAppiumDriverCommand (cmd) {
|
589 | return !isSessionCommand(cmd) || cmd === 'deleteSession';
|
590 | }
|
591 |
|
592 | export { AppiumDriver };
|