diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index c3eda9e3d..222ada79d 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -978,11 +978,23 @@ class Appium extends Webdriver { */ async setNetworkConnection(value) { onlyForApps.call(this, supportedPlatform.android) - return this.browser.execute('mobile: setConnectivity', { + const connectivity = { airplaneMode: !!(value & 1), wifi: !!(value & 2), data: !!(value & 4), - }) + } + // `mobile: setConnectivity` runs `adb shell svc data ` for every field it receives, + // which fails with "Can't find service: phone" on images without telephony (e.g. tablets). + // Only a positive result is cached: a freshly booted device may not have registered a carrier + // yet, and caching that would strip `data` for the whole session. + if (!this._hasTelephony) { + const deviceInfo = await this.browser.execute('mobile: deviceInfo') + this._hasTelephony = !!deviceInfo?.carrierName + } + // Keep `data` on telephony-capable devices, otherwise the device stays online over cellular + // and going offline silently does nothing. + if (!this._hasTelephony) delete connectivity.data + return this.browser.execute('mobile: setConnectivity', connectivity) } /** diff --git a/test/unit/helper/Appium_networkConnection_test.js b/test/unit/helper/Appium_networkConnection_test.js index 1930fd47a..7299b28a7 100644 --- a/test/unit/helper/Appium_networkConnection_test.js +++ b/test/unit/helper/Appium_networkConnection_test.js @@ -2,7 +2,7 @@ import { expect } from 'chai' import sinon from 'sinon' import Appium from '../../../lib/helper/Appium.js' -function createApp() { +function createApp(deviceInfo = { carrierName: 'T-Mobile' }) { const app = new Appium({ platform: 'Android', desiredCapabilities: { @@ -10,6 +10,7 @@ function createApp() { }, }) app.browser = { execute: sinon.stub() } + app.browser.execute.withArgs('mobile: deviceInfo').resolves(deviceInfo) return app } @@ -44,6 +45,29 @@ describe('Appium #setNetworkConnection, #grabNetworkConnection', () => { expect(app.browser.execute.calledWith('mobile: setConnectivity', { airplaneMode: false, wifi: false, data: false })).to.be.true }) + it('should omit data on devices without telephony', async () => { + const app = createApp({ carrierName: '' }) + await app.setNetworkConnection(1) + expect(app.browser.execute.calledWith('mobile: setConnectivity', { airplaneMode: true, wifi: false })).to.be.true + }) + + it('should probe telephony again while no carrier was seen yet', async () => { + const app = createApp({ carrierName: '' }) + await app.setNetworkConnection(1) + app.browser.execute.withArgs('mobile: deviceInfo').resolves({ carrierName: 'T-Mobile' }) + await app.setNetworkConnection(2) + expect(app.browser.execute.withArgs('mobile: deviceInfo').callCount).to.equal(2) + expect(app.browser.execute.calledWith('mobile: setConnectivity', { airplaneMode: false, wifi: true, data: false })).to.be.true + }) + + it('should probe telephony only once after a carrier was seen', async () => { + const app = createApp() + await app.setNetworkConnection(1) + await app.setNetworkConnection(6) + expect(app.browser.execute.withArgs('mobile: deviceInfo').callCount).to.equal(1) + expect(app.browser.execute.calledWith('mobile: setConnectivity', { airplaneMode: false, wifi: true, data: true })).to.be.true + }) + it('should grab network connection using mobile: getConnectivity and map to legacy bitmask shape', async () => { const app = createApp() app.browser.execute.resolves({ airplaneMode: false, wifi: false, data: true })