UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

207 lines 8.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sessionClaimHandler = exports.SessionClaimHandler = void 0; exports.setSharedIpcForTesting = setSharedIpcForTesting; exports.resetDriverInstanceIpcForTesting = resetDriverInstanceIpcForTesting; const promises_1 = require("node:timers/promises"); const support_1 = require("appium/support"); const asyncbox_1 = require("asyncbox"); const utils_1 = require("./utils"); class SessionClaimHandler { getIpc; static CLAIMED_TOPIC = 'xcuitest:sessionUdidClaimed'; static CONTENDED_TOPIC = 'xcuitest:sessionUdidContended'; static RELEASED_TOPIC = 'xcuitest:sessionUdidReleased'; static CONTENTION_PROBE_MS = 10; static RELEASE_WAIT_MS = 15000; subscriptionsBySessionId = new Map(); constructor(getIpc) { this.getIpc = getIpc; } /** Subscribe the current session to udid claim messages from other sessions. */ async registerActiveSession(driver) { const ipc = await this.getIpc(); const udid = driver.opts.udid; const sessionId = driver.sessionId; if (!ipc || !udid || !sessionId) { return; } this.unregisterActiveSession(driver); const subscription = ipc.subscribe(SessionClaimHandler.CLAIMED_TOPIC, this.getPublisherId(driver)); subscription.on('message', (message) => { void this.dispatchSessionUdidMessage(driver, udid, sessionId, message); }); this.subscriptionsBySessionId.set(sessionId, subscription); } /** Unsubscribe the current session from udid claim messages. */ unregisterActiveSession(driver) { const sessionId = driver.sessionId; if (!sessionId) { return; } this.subscriptionsBySessionId.get(sessionId)?.unsubscribe(); this.subscriptionsBySessionId.delete(sessionId); } /** Publish this session's udid so any existing session on the same device can terminate. */ async claimSessionUdid(driver) { const ipc = await this.getIpc(); if (!ipc) { driver.log.debug('Driver-instance IPC is unavailable. Skipping publication of the session udid.'); return; } const udid = driver.opts.udid; const sessionId = driver.sessionId; if (!udid || !sessionId) { driver.log.debug('The session udid is not known yet. Skipping udid publication.'); return; } const contendingSessionIds = new Set(); const releasedSessionIds = new Set(); const contendedSubscription = ipc.subscribe(SessionClaimHandler.CONTENDED_TOPIC, this.getPublisherId(driver)); const releasedSubscription = ipc.subscribe(SessionClaimHandler.RELEASED_TOPIC, this.getPublisherId(driver)); contendedSubscription.on('message', (message) => { if (this.isMatchingSessionUdidMessage(message, udid, sessionId)) { contendingSessionIds.add(message.data.sessionId); } }); releasedSubscription.on('message', (message) => { if (this.isMatchingSessionUdidMessage(message, udid, sessionId)) { releasedSessionIds.add(message.data.sessionId); } }); try { await ipc.publish(SessionClaimHandler.CLAIMED_TOPIC, this.getPublisherId(driver), { udid, sessionId, }); await (0, promises_1.setTimeout)(SessionClaimHandler.CONTENTION_PROBE_MS); if (contendingSessionIds.size === 0) { return; } try { await (0, asyncbox_1.waitForCondition)(() => [...contendingSessionIds].every((id) => releasedSessionIds.has(id)), { waitMs: SessionClaimHandler.RELEASE_WAIT_MS, intervalMs: 50, }); driver.log.debug(`Received release confirmation from ` + `${support_1.util.pluralize('session', contendingSessionIds.size, true)} for udid '${udid}'`); } catch { const pendingSessionIds = [...contendingSessionIds].filter((id) => !releasedSessionIds.has(id)); driver.log.warn(`Timed out after ${SessionClaimHandler.RELEASE_WAIT_MS}ms waiting for ` + `${support_1.util.pluralize('session', pendingSessionIds.length, true)} ` + `[${pendingSessionIds.join(', ')}] to release udid '${udid}'. ` + `Proceeding with session startup.`); } } finally { contendedSubscription.unsubscribe(); releasedSubscription.unsubscribe(); } } /** @internal Exposed for unit tests. */ resetForTesting() { for (const subscription of this.subscriptionsBySessionId.values()) { subscription.unsubscribe(); } this.subscriptionsBySessionId.clear(); } async dispatchSessionUdidMessage(driver, udid, sessionId, message) { try { await this.handleSessionUdidMessage(driver, udid, message); } catch (err) { const msg = err instanceof Error ? err.message : String(err); driver.log.warn(`Could not handle udid claim IPC message for session '${sessionId}': ${msg}`); } } async handleSessionUdidMessage(driver, udid, message) { if (!this.isMatchingSessionUdidMessage(message, udid, driver.sessionId ?? undefined)) { return; } try { await this.publishSessionUdidContended(driver, udid); } catch (err) { const msg = err instanceof Error ? err.message : String(err); driver.log.warn(`Could not publish udid contention message for session '${driver.sessionId}': ${msg}`); } driver.log.warn(`Session '${message.data.sessionId}' is starting on udid '${udid}', which is already in use ` + `by another session identified by ${driver.sessionId}. Running multiple parallel sessions on the same ` + `device is highly discouraged. Consider enabling the Appium server's '--session-override' flag ` + `and make sure to properly quit the previous session before starting a new one. ` + `Terminating the obsolete session.`); await this.terminateSessionOnRequest(driver, udid); } async publishSessionUdidContended(driver, udid) { const ipc = await this.getIpc(); const sessionId = driver.sessionId ?? undefined; if (!ipc || !udid || !sessionId) { return; } await ipc.publish(SessionClaimHandler.CONTENDED_TOPIC, this.getPublisherId(driver), { udid, sessionId, }); } async terminateSessionOnRequest(driver, udid) { const sessionId = driver.sessionId ?? undefined; const publisherId = sessionId ? this.getPublisherId(driver) : undefined; const { log } = driver; try { await driver.deleteSession(); } catch (err) { const msg = err instanceof Error ? err.message : String(err); log.warn(`Could not terminate session '${sessionId}' on IPC request: ${msg}`); } await this.publishSessionUdidReleased(log, udid, sessionId, publisherId); } async publishSessionUdidReleased(log, udid, sessionId, publisherId) { try { const ipc = await this.getIpc(); if (!ipc || !udid || !sessionId || !publisherId) { return; } await ipc.publish(SessionClaimHandler.RELEASED_TOPIC, publisherId, { udid, sessionId, }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); log.warn(`Could not publish udid release message for session '${sessionId}': ${msg}`); } } getPublisherId(driver) { return support_1.node.getObjectId(driver); } isMatchingSessionUdidMessage(message, udid, sessionId) { return message.data.udid === udid && message.data.sessionId !== sessionId; } } exports.SessionClaimHandler = SessionClaimHandler; const loadSharedIpc = (0, utils_1.memoize)(async function loadSharedIpc() { try { const { AppiumIpc } = (await import('appium/driver.js')); return AppiumIpc ? new AppiumIpc() : undefined; } catch { return undefined; } }); exports.sessionClaimHandler = new SessionClaimHandler(loadSharedIpc); /** * @internal Exposed for unit tests. */ function setSharedIpcForTesting(ipc) { loadSharedIpc.cache.set(undefined, Promise.resolve(ipc)); } /** * @internal Exposed for unit tests. */ function resetDriverInstanceIpcForTesting() { exports.sessionClaimHandler.resetForTesting(); loadSharedIpc.cache.clear(); } //# sourceMappingURL=session-claim-handler.js.map